Compare commits

...

4 commits

13 changed files with 142 additions and 159 deletions

View file

@ -3,3 +3,33 @@
[![status-badge](https://ci.patagia.dev/api/badges/42/status.svg)](https://ci.patagia.dev/repos/42)
PatOS is a minimal, immutable Linux distribution specialized for the Patagia Platform.
## Features
* Immutable read-only system
* Trust chain verification using secure boot and dm-verity
* Automatic updates and unattended rollbacks
* Host/TPM bound data encryption by default
## Roadmap and status
| Step | Status |
| --------------------------------------------------------- | :----: |
| A/B root partitions with discoverable partitions | ✅ |
| Verity protection of root partitions | ✅ |
| Encrypted state partition with tpm2 host binding | ✅ |
| Automatic updates | ⚠️ |
| Installer | ❌ |
| Machine registration and provisioning | ❌ |
| Boot assessment w/automatic rollback | ❌ |
## Community
* Source code: https://patagia.dev/Patagia/patos
## License
Copyright 2024 Patagia
Unless otherwise noted, all components are licenced under the Mozilla Public License Version 2.0.

View file

@ -14,6 +14,10 @@
pkgs = import nixpkgs { inherit system; };
in
{
nixosModules.devel.imports = [
./modules/profiles/devel.nix
];
nixosModules.server.imports = [
./modules/profiles/server.nix
];
@ -25,6 +29,27 @@
];
packages.${system} = {
devel =
(nixpkgs.lib.nixosSystem {
modules = [
(
{ lib, ... }:
{
nixpkgs.hostPlatform = system;
system.stateVersion = "24.05";
}
)
{
system.image.updates.url = "${updateUrl}";
system.image.id = "patos";
system.image.version = releaseVersion;
image.compress = false;
}
self.nixosModules.image
self.nixosModules.devel
];
}).config.system.build.updatePackage;
patos =
(nixpkgs.lib.nixosSystem {
modules = [
@ -49,7 +74,6 @@
};
checks.${system} = {
ssh-preseed = import ./tests/ssh-preseed.nix { inherit pkgs self; };
podman = import ./tests/podman.nix { inherit pkgs self; };
system-update = import ./tests/system-update.nix { inherit pkgs self; };
};

View file

@ -15,5 +15,13 @@ build: build-image
build-image:
nix build .#patos
# Build PatOS image (developer mode)
build-devel-image:
nix build .#devel
run: build-image
qemu-uefi-tpm ./result/*.img
run-devel: build-devel-image
qemu-uefi-tpm ./result/*.img

View file

@ -76,9 +76,6 @@ let
contents = {
"/EFI/BOOT/BOOT${lib.toUpper efiArch}.EFI".source = "${pkgs.systemdUkify}/lib/systemd/boot/efi/systemd-boot${efiArch}.efi";
"/EFI/Linux/${config.system.boot.loader.ukiFile}".source = "${config.system.build.uki}/${config.system.boot.loader.ukiFile}";
"/default-ssh-authorized-keys.txt" = lib.mkIf config.system.image.sshKeys.enable {
source = pkgs.writeText "ssh-keys" (lib.concatStringsSep "\n" config.system.image.sshKeys.keys);
};
};
repartConfig = {
Type = "esp";

View file

@ -8,7 +8,6 @@
imports = [
./updater.nix
./ssh.nix
./builder.nix
./veritysetup.nix
];
@ -51,11 +50,16 @@
Label = "_empty";
ReadOnly = 1;
};
"40-home" = {
Type = "home";
"40-var" = {
Type = "var";
UUID = "4d21b016-b534-45c2-a9fb-5c16e091fd2d"; # Well known
Format = "btrfs";
SizeMinBytes = "512M";
Label = "patos-state";
Minimize = "off";
FactoryReset = "yes";
Encrypt = "tpm2";
SizeMinBytes = "2G";
SplitName = "-";
};
};
@ -99,10 +103,21 @@
"roothash=${config.system.build.verityRootHash}"
];
fileSystems."/var" = {
fsType = "tmpfs";
options = [ "mode=0755" ];
};
fileSystems =
let
parts = config.systemd.repart.partitions;
in
{
"/var" = {
fsType = parts."40-var".Format;
device = "/dev/mapper/var";
encrypted = {
enable = true;
blkDev = "/dev/disk/by-partuuid/${parts."40-var".UUID}";
label = "var";
};
};
};
# Required to mount the efi partition
boot.kernelModules = [
@ -111,14 +126,6 @@
"nls_iso8859-1"
];
# Store SSH host keys on /home since /etc is read-only
services.openssh.hostKeys = [
{
path = "/home/.ssh/ssh_host_ed25519_key";
type = "ed25519";
}
];
environment.etc."machine-id" = {
text = "";
mode = "0755";
@ -126,8 +133,4 @@
# Refuse to boot on mount failure
systemd.targets."sysinit".requires = [ "local-fs.target" ];
# Make sure home gets mounted
systemd.targets."local-fs".requires = [ "home.mount" ];
}

View file

@ -1,40 +0,0 @@
{ config, lib, ... }:
{
options.system.image.sshKeys = {
enable = lib.mkEnableOption "provisioning of default SSH keys from ESP";
keys = lib.mkOption {
type = lib.types.listOf lib.types.singleLineStr;
default = [ ];
};
};
config = lib.mkIf config.system.image.sshKeys.enable {
assertions = [
{
assertion = config.services.openssh.enable;
message = "OpenSSH must be enabled to preseed authorized keys";
}
];
systemd.services."default-ssh-keys" = {
script = ''
mkdir -p /home/admin/.ssh/
cat /efi/default-ssh-authorized-keys.txt >> /home/admin/.ssh/authorized_keys
'';
wantedBy = [
"sshd.service"
"sshd.socket"
];
unitConfig = {
ConditionPathExists = [
"/home/admin"
"!/home/admin/.ssh/authorized_keys"
"/efi/default-ssh-authorized-keys.txt"
];
};
};
};
}

View file

@ -45,10 +45,9 @@
# FIXME: fstrim should only be enabled for virtual machine images?
services.fstrim.enable = true;
services.openssh.settings.PasswordAuthentication = lib.mkDefault false;
users.allowNoPasswordLogin = true;
users.users.root.home = lib.mkForce "/";
security.sudo.enable = lib.mkDefault false;
security.polkit = {
@ -65,23 +64,16 @@
i18n.supportedLocales = [ "en_US.UTF-8/UTF-8" ];
# Console
# FIXME: Add option for toggle
# console.enable = false;
# systemd.services."getty@tty1".enable = lib.mkDefault false;
# systemd.services."autovt@".enable = lib.mkDefault false;
systemd.enableEmergencyMode = false;
boot.consoleLogLevel = lib.mkDefault 1;
boot.kernelParams = [
# "quiet"
"panic=1"
"boot.panic_on_fail"
"nomodeset"
"console=tty1"
"console=ttyS0,38400"
"systemd.log_level=info"
"systemd.log_target=console"
"systemd.journald.forward_to_console"
# "nomodeset"
"console=ttyS0,115200n8"
"earlyprintk=ttyS0,115200n8"
"systemd.mask=systemd-vconsole-setup.service" # FIXME: Figure out why vconsole-setup fails when loading keymap
];
# This is vi country

View file

@ -0,0 +1,39 @@
{
modulesPath,
...
}:
{
imports = [ ./server.nix ];
boot.kernel.sysctl = {
"net.ipv4.ip_unprivileged_port_start" = 0;
};
boot.kernelParams = [
"systemd.log_level=info"
"systemd.log_target=console"
"systemd.journald.forward_to_console"
];
users.users."admin" = {
isNormalUser = true;
linger = true;
extraGroups = [ "wheel" ];
home = "/var/home/admin";
};
environment.etc = {
subuid = {
text = "admin:100000:65536";
mode = "0644";
};
subgid = {
text = "admin:100000:65536";
mode = "0644";
};
};
services.getty.autologinUser = "admin";
}

View file

@ -10,34 +10,8 @@
./sysext.nix
];
boot.kernel.sysctl = {
"net.ipv4.ip_unprivileged_port_start" = 0;
};
users.users."admin" = {
isNormalUser = true;
linger = true;
extraGroups = [ "wheel" ];
};
environment.etc = {
subuid = {
text = "admin:100000:65536";
mode = "0644";
};
subgid = {
text = "admin:100000:65536";
mode = "0644";
};
};
services.openssh.enable = true;
system.image.sshKeys.enable = true;
system.image.sshKeys.keys = [
"sk-ssh-ed25519@openssh.com AAAAGnNrLXNzaC1lZDI1NTE5QG9wZW5zc2guY29tAAAAIIHMAEZx02kbHrEygyPQYStiXlrIe6EIqBCv7anIkL0pAAAABHNzaDo= dln1"
"sk-ssh-ed25519@openssh.com AAAAGnNrLXNzaC1lZDI1NTE5QG9wZW5zc2guY29tAAAAIJNOBFoU7Cdsgi4KpYRcv7EhR/8kD4DYjEZnwk6urRx7AAAABHNzaDo= dln2"
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIKDx+7ZEJi7lUCAtoHRRIduJzH3hrpx4YS1f0ZxrJ+uW dln3"
boot.kernelParams = [
"quiet"
];
virtualisation.podman.enable = true;

View file

@ -11,13 +11,13 @@
"systemd-sysext.service"
];
systemd.services."systemd-confext" = {
enable = true;
wantedBy = [ "multi-user.target" ];
};
# systemd.services."systemd-confext" = {
# enable = true;
# wantedBy = [ "multi-user.target" ];
# };
systemd.services."systemd-sysext.service" = {
enable = true;
wantedBy = [ "multi-user.target" ];
};
# systemd.services."systemd-sysext.service" = {
# enable = true;
# wantedBy = [ "multi-user.target" ];
# };
}

View file

@ -1,7 +0,0 @@
{ prev, ... }:
prev.openssh.overrideAttrs (final: prev: {
doCheck = false;
doInstallCheck = false;
dontCheck = true;
})

View file

@ -42,7 +42,7 @@ rec {
boot.initrd.compressorArgs = lib.mkForce [ "-8" ];
}
(pkgs.path + "/nixos/modules/testing/test-instrumentation.nix")
self.nixosModules.server
self.nixosModules.devel
self.nixosModules.image
extraConfig
];

View file

@ -1,37 +0,0 @@
{ pkgs, self }:
let
lib = pkgs.lib;
test-common = import ./common.nix { inherit self lib pkgs; };
sshKeys = import (pkgs.path + "/nixos/tests/ssh-keys.nix") pkgs;
image = test-common.makeImage {
system.image.sshKeys.keys = [ sshKeys.snakeOilPublicKey ];
system.extraDependencies = [ sshKeys.snakeOilPrivateKey ];
};
in
test-common.makeImageTest {
name = "ssh-preseed";
inherit image;
script = ''
start_tpm()
machine.start()
machine.wait_for_unit("multi-user.target")
machine.succeed("[ -e /efi/default-ssh-authorized-keys.txt ]")
machine.succeed("[ -e /home/admin/.ssh/authorized_keys ]")
machine.wait_for_open_port(22)
machine.succeed(
"cat ${sshKeys.snakeOilPrivateKey} > privkey.snakeoil"
)
machine.succeed("chmod 600 privkey.snakeoil")
machine.succeed(
"ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -i privkey.snakeoil admin@127.0.0.1 true",
timeout=30
)
'';
}