Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
115 changes: 115 additions & 0 deletions modules/checks/stibnite-access-wiring.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
# Structural check for stibnite's aarch64-darwin build access.
#
# It guards the pairing between two machines' configurations, which no single
# machine's evaluation can catch. magnetite generates two keypairs and stibnite
# authorizes them under two different accounts; swapping them, or authorizing
# the build key without its forced command, produces a configuration that
# builds and activates and is wrong in the one way that matters — the build
# key would carry an interactive login, or the independently revocable
# identities would be assigned to the wrong accounts.
#
# It also pins the two mechanisms to one account: the nix.buildMachines entry
# (result copied back to the caller) and the remote-store URI (build stays in
# stibnite's store) must name the same user and the same ssh alias, or one of
# them silently reaches an account that authorizes nothing.
{
self,
lib,
...
}:
{
perSystem =
{ pkgs, system, ... }:
let
mkCheck = self.lib.mkStructuralCheck pkgs;

magnetite = self.nixosConfigurations.magnetite.config;
stibnite = self.darwinConfigurations.stibnite.config;

buildKey =
lib.removeSuffix "\n"
magnetite.clan.core.vars.generators.stibnite-nix-build.files."key.pub".value;
sessionKey =
lib.removeSuffix "\n"
magnetite.clan.core.vars.generators.stibnite-agent-session.files."key.pub".value;

authorized = user: stibnite.environment.etc."ssh/nix_authorized_keys.d/${user}".text;
authorizedLines = user: builtins.filter (line: line != "") (lib.splitString "\n" (authorized user));

darwinEntries = builtins.filter (
m: builtins.elem "aarch64-darwin" m.systems
) magnetite.nix.buildMachines;
darwinEntry = builtins.head darwinEntries;

# The forced command names an activation-time store path, so the oracle
# asserts its shape: the key options, and that the program is the
# nix-daemon of the nix package stibnite activates.
buildLine = builtins.head (authorizedLines "nixbuild");
forcedCommand = ''restrict,command="${stibnite.nix.package}/bin/nix-daemon --stdio"'';
in
{
checks =
lib.optionalAttrs
(builtins.elem system [
"x86_64-linux"
"aarch64-darwin"
])
{
stibnite-access-wiring = mkCheck {
name = "stibnite-access-wiring";
actual = {
darwinBuilderCount = builtins.length darwinEntries;
darwinBuilder = {
inherit (darwinEntry)
hostName
sshUser
protocol
systems
maxJobs
supportedFeatures
;
};

# Both mechanisms, one account and one alias.
storeUriNamesBuildAccount =
magnetite.environment.etc."nix/stibnite-store-uri".text
== "ssh-ng://${darwinEntry.sshUser}@${darwinEntry.hostName}?ssh-key=${darwinEntry.sshKey}\n";

# Key separation, asserted in both directions.
buildAccountAuthorizes = authorizedLines "nixbuild";
sessionKeyIsNotABuildKey = !(lib.hasInfix sessionKey (authorized "nixbuild"));
buildKeyIsNotASessionKey = !(lib.hasInfix buildKey (authorized "crs58"));
sessionKeyAuthorizedForSessions = lib.hasInfix sessionKey (authorized "crs58");

# The build key carries a forced command and nothing else.
buildLineIsRestrictedToTheProtocol = buildLine == "${forcedCommand} ${buildKey}";

buildAccountIsTrusted = builtins.elem "nixbuild" stibnite.nix.settings.trusted-users;
buildAccountIsManaged = builtins.elem "nixbuild" stibnite.users.knownUsers;
};
expected = {
darwinBuilderCount = 1;
darwinBuilder = {
hostName = "stibnite-builder";
sshUser = "nixbuild";
protocol = "ssh-ng";
systems = [ "aarch64-darwin" ];
maxJobs = 4;
supportedFeatures = [
"apple-virt"
"big-parallel"
];
};
storeUriNamesBuildAccount = true;
buildAccountAuthorizes = [ "${forcedCommand} ${buildKey}" ];
sessionKeyIsNotABuildKey = true;
buildKeyIsNotASessionKey = true;
sessionKeyAuthorizedForSessions = true;
buildLineIsRestrictedToTheProtocol = true;
buildAccountIsTrusted = true;
buildAccountIsManaged = true;
};
};
};
};
}
22 changes: 22 additions & 0 deletions modules/machines/darwin/stibnite/default.nix
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ in
zt-dns
zt-services-trust
magnetite-builder
stibnite-build-host
# Not importing users module (defines testuser at UID 550)
# stibnite defines its own user (crs58)
]);
Expand Down Expand Up @@ -224,6 +225,27 @@ in
# Offload native x86_64-linux builds to magnetite over ZeroTier.
services.magnetite-builder.enable = true;

# Inbound side of the same asymmetry: stibnite is the fleet's only
# aarch64-darwin machine, so it serves darwin builds to hosts that cannot
# perform them. The two keypairs come from magnetite and are authorized
# separately for independent revocation and rotation. The build key is
# confined at the SSH boundary to the nix protocol. The session key is
# broader: it logs in as admin-group crs58, a Nix trusted user, so it has
# build authority plus shell access.
# Both encrypted private halves and public values are committed under
# vars/per-machine/magnetite/. `clan vars generate magnetite` populates
# them; only magnetite and authorized users can decrypt the private
# halves.
services.stibnite-build-host = {
enable = true;
buildKeys = [
inputs.self.nixosConfigurations.magnetite.config.clan.core.vars.generators.stibnite-nix-build.files."key.pub".value
];
sessionKeys = [
inputs.self.nixosConfigurations.magnetite.config.clan.core.vars.generators.stibnite-agent-session.files."key.pub".value
];
};

# Colima for OCI container management (complementary to nix-rosetta-builder)
services.colima = {
enable = true;
Expand Down
26 changes: 26 additions & 0 deletions modules/machines/nixos/magnetite/default.nix
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ in
hm-sops-bridge
niks3
ssh-known-hosts
stibnite-builder
stibnite-session
buildbot
nixbot
gitea
Expand Down Expand Up @@ -82,6 +84,30 @@ in
];
};

# magnetite is x86_64-linux and cannot build aarch64-darwin derivations,
# so darwin work is dispatched to stibnite, the fleet's only machine of
# that system. Two mechanisms, two callers:
# nix.buildMachines below — a local nix build by an operator who wants
# the darwin result in magnetite's store.
# /etc/nix/stibnite-store-uri — a caller that wants the build to happen
# entirely in stibnite's store with nothing copied back.
# nixbot.toml sets attribute = "checks.x86_64-linux", which prevents CI
# from evaluating or requesting aarch64-darwin work and makes this builder
# unreachable from CI. modules/nixos/nixbot.nix and
# modules/nixos/buildbot.nix each set buildSystems = [ "x86_64-linux" ]
# as an independent second layer. These controls remain because stibnite
# is a laptop without guaranteed availability and a sleeping machine
# could gate CI.
services.stibnite-builder.enable = true;
nix.buildMachines = config.services.stibnite-builder.buildMachines;

# The build and session keys are separately authorized for independent
# revocation and rotation. Only the build key is confined to the Nix
# protocol by `restrict` and a forced command. The session key is broader:
# it grants an unrestricted login as admin-group crs58, already a Nix
# trusted user, with build authority plus shell.
services.stibnite-session.enable = true;

# User configuration managed via clan inventory users service (modules/clan/inventory/services/users/cameron.nix).

security.sudo.wheelNeedsPassword = false;
Expand Down
Loading