diff --git a/flake.lock b/flake.lock index 26330790a..15bb3e8ca 100644 --- a/flake.lock +++ b/flake.lock @@ -310,16 +310,17 @@ }, "impermanence": { "locked": { - "lastModified": 1731242966, - "narHash": "sha256-B3C3JLbGw0FtLSWCjBxU961gLNv+BOOBC6WvstKLYMw=", + "lastModified": 1728049659, + "narHash": "sha256-lGtad92Y/TnqpXRlZ1syiEq5czpvblKmcypeqGPiVF4=", "owner": "nix-community", "repo": "impermanence", - "rev": "3ed3f0eaae9fcc0a8331e77e9319c8a4abd8a71a", + "rev": "32b1094d28d5fbedcc85a403bc08c8877b396255", "type": "github" }, "original": { "owner": "nix-community", "repo": "impermanence", + "rev": "32b1094d28d5fbedcc85a403bc08c8877b396255", "type": "github" } }, diff --git a/flake.nix b/flake.nix index c30670bfa..952a5a684 100644 --- a/flake.nix +++ b/flake.nix @@ -140,7 +140,7 @@ }; impermanence = { - url = "github:nix-community/impermanence"; + url = "github:nix-community/impermanence/32b1094d28d5fbedcc85a403bc08c8877b396255"; }; givc = { diff --git a/modules/common/default.nix b/modules/common/default.nix index 26011fa03..6ce4a0b3a 100644 --- a/modules/common/default.nix +++ b/modules/common/default.nix @@ -11,7 +11,7 @@ ./firewall ./profiles ./security - ./users/accounts.nix + ./users ./version ./virtualization/docker.nix ./systemd diff --git a/modules/common/profiles/debug.nix b/modules/common/profiles/debug.nix index 21a5194f1..3b7020f64 100644 --- a/modules/common/profiles/debug.nix +++ b/modules/common/profiles/debug.nix @@ -15,7 +15,6 @@ in config = lib.mkIf cfg.enable { # Enable default accounts and passwords ghaf = { - users.accounts.enable = true; # Enable development on target development = { nix-setup.enable = true; diff --git a/modules/common/profiles/release.nix b/modules/common/profiles/release.nix index f06a6a72f..056007f54 100644 --- a/modules/common/profiles/release.nix +++ b/modules/common/profiles/release.nix @@ -18,6 +18,6 @@ in # TODO this needs to be refined when we define a policy for the # processes and the UID/groups that should be enabled by default # if not already covered by systemd - ghaf.users.accounts.enable = true; + # ghaf.users.admin.enable = true; }; } diff --git a/modules/common/services/audio.nix b/modules/common/services/audio.nix index 00d0e541a..ff4cd36c7 100644 --- a/modules/common/services/audio.nix +++ b/modules/common/services/audio.nix @@ -81,13 +81,6 @@ in }; }; - # Allow ghaf user to access pulseaudio and pipewire - users.extraUsers.ghaf.extraGroups = [ - "audio" - "video" - "pipewire" - ]; - # Start pipewire on system boot systemd.services.pipewire.wantedBy = [ "multi-user.target" ]; diff --git a/modules/common/services/fprint.nix b/modules/common/services/fprint.nix index 87654b43f..e13eabf05 100644 --- a/modules/common/services/fprint.nix +++ b/modules/common/services/fprint.nix @@ -46,38 +46,19 @@ in // Allow user to verify fingerprints polkit.addRule(function(action, subject) { if (action.id == "net.reactivated.fprint.device.verify" && - subject.user == "ghaf") { + subject.isInGroup ("users")) { return polkit.Result.YES; } }); // Allow user to enroll fingerprints polkit.addRule(function(action, subject) { if (action.id == "net.reactivated.fprint.device.enroll" && - subject.user == "ghaf") { + subject.isInGroup ("users")) { return polkit.Result.YES; } }); ''; }; - # PAM rules for swaylock fingerprint reader - pam.services = { - swaylock.text = '' - # Account management. - account required pam_unix.so - - # Authentication management. - auth sufficient pam_unix.so likeauth try_first_pass - auth sufficient ${pkgs.fprintd}/lib/security/pam_fprintd.so - auth required pam_deny.so - - # Password management. - password sufficient pam_unix.so nullok sha512 - - # Session management. - session required pam_env.so conffile=/etc/pam/environment readenv=0 - session required pam_unix.so - ''; - }; }; }; } diff --git a/modules/common/services/xdgopener.nix b/modules/common/services/xdgopener.nix index a7e7985d4..cc0b726b1 100644 --- a/modules/common/services/xdgopener.nix +++ b/modules/common/services/xdgopener.nix @@ -22,6 +22,7 @@ let # into all targets ghaf-xdg-open = pkgs.callPackage ../../../packages/ghaf-xdg-open { inherit (config.ghaf.security.sshKeys) sshKeyPath; + user = config.ghaf.users.appUser.name; }; in { @@ -51,9 +52,6 @@ in services."xdg@" = { description = "XDG opener"; serviceConfig = { - # The user 'ghaf' is used here to access SSH keys for the scp command - # This is required to copy files to the zathuravm - User = "ghaf"; ExecStart = "${ghaf-xdg-open}/bin/ghaf-xdg-open"; StandardInput = "socket"; StandardOutput = "journal"; diff --git a/modules/common/systemd/base.nix b/modules/common/systemd/base.nix index 786542ca4..4a6706923 100644 --- a/modules/common/systemd/base.nix +++ b/modules/common/systemd/base.nix @@ -30,10 +30,11 @@ let inherit (cfg) withAudit; withCompression = true; withCoredump = cfg.withDebug || cfg.withMachines; - inherit (cfg) withCryptsetup; + withCryptsetup = cfg.withCryptsetup || cfg.withHomed; inherit (cfg) withEfi; inherit (cfg) withBootloader; inherit (cfg) withFido2; + inherit (cfg) withHomed; inherit (cfg) withHostnamed; withImportd = cfg.withMachines; withKexectools = cfg.withDebug; @@ -55,6 +56,7 @@ let inherit (cfg) withTimesyncd; inherit (cfg) withTpm2Tss; inherit (cfg) withUkify; + withUserDb = cfg.withHomed; withUtmp = cfg.withJournal || cfg.withAudit; } // lib.optionalAttrs (lib.strings.versionAtLeast pkgs.systemdMinimal.version "255.0") { @@ -230,6 +232,12 @@ in default = false; }; + withHomed = mkOption { + description = "Enable systemd homed for users home functionality."; + type = types.bool; + default = false; + }; + withHostnamed = mkOption { description = "Enable systemd hostname daemon."; type = types.bool; diff --git a/modules/common/users/README.md b/modules/common/users/README.md new file mode 100644 index 000000000..0298088ac --- /dev/null +++ b/modules/common/users/README.md @@ -0,0 +1,101 @@ +# Ghaf user setup + +## Rationale +The changes address the separation between declarative and runtime +users. Declarative user definitions are appropriate if their +configuration should be the same across different machines. +Non-declarative users are introduced that allow changes without +re-building the configuration, and are purely device specific. + +For more consistent declarative user management, 'userborn' is +introduced, which increases consistency by managing user parameter +changes across re-builds. It is enabled for host and all VMs by default. +The NixOS configuration `users.mutableUsers` is set to false, thus +the generic user tools cannot be used at runtime (`passwd`, `useradd`, +etc.). + +To manage non-declarative users, 'systemd-homed' is used. It offers a +variety of concepts that are in line with our requirements such as +on-the-fly user creation, encryption, CIFS-integration, and more. + +## Code structure + +common.nix - common settings +admin.nix - admin account settings +desktop.nix - user accounts to manage desktop +other.nix - template to add other declarative users + +## Accounts +All accounts are available to be configured. Note that they are only +available in the respective VM where they are specifically enabled, with +exception for the admin account. + +### Admin account +The 'ghaf' user account is now the admin account (+wheel), and enabled by +default in host and all VMs. As this account is for administrative +purposes, it should not run a desktop session. This currently works with +some limitations, but at the moment we do not run a full-fletched multi-user +system. + +### Login/desktop user account +The 'loginUser' account can be enabled and sets up a non-declarative +user with a reserved UID. It is a self-contained account, that currently +runs the user desktop session. + +### Auxiliary accounts +Two auxiliary accounts are available that share the login users UID to +keep these UIDs consistent across VMs: + 1) Proxy user + This user is used in system VMs that provide services. These services + are currently accessible via the dbus proxy, and require the same UID. + 2) App user + This user is used in app VMs to run the user sessions (including applications). + The shared UID is helpful to map access rights across machines and support + legacy functionality. + +### Other (declarative/managed) user accounts +While any additional user accounts may be freely created and administered, a template +for configuration managed users is provided. + +## Future work +This patch introduces new account management on Ghaf. Based on the changes, +future work is required to extend it. + +### Extending login-user functionality +Currently, a minimalistic setup script runs on first GUI-VM boot. The login +user setup may be extended with: + + - Improvement of user creation script + - Graphical interface for user creation + - Feature integration: (supported by homed) + - CIFS/remote user storage integration + - FIDO token integration + - User ssh keys + - Avatar/Background/Locale/Timezone/Location + - External home support (e.g., USB or network storage) + - Potential re-work to run graphical session as static user + - Potential multi-user system with migratable data + +### Overall user account improvements + +- removing (hardcoded) ssh dependencies +- removing ssh root access, e.g., update user (PoC available) +- centralized mechanism (profiles) to administer declarative user account data (e.g., passwords) +- password policies mechanism, especially considering declarative users passwords are in nix store + +### User data implications +While the login user setup provides some containment, currently user data is still +spread across the system, such as + - Persistent user-related platform data (e.g., wifi passwords) + - Persistent user data in app VMs (e.g., browser profiles/passwords) + - File sharing between VMs + +Respective mechanisms are currently under investigation. + +## Implementation notes + + - VM storage shares have been re-named for consistency (-vm) + - impermanence flake input pinned to userborn patch + - /etc/machine-id (gui-vm) is currently hardcoded as login user identity file depends on it. + It should be generated on first boot and persistet. Workaround is available upstream (after + userborn patch) in impermanence but does not seem to work with our setup, investigation required diff --git a/modules/common/users/accounts.nix b/modules/common/users/accounts.nix deleted file mode 100644 index 01b24aa20..000000000 --- a/modules/common/users/accounts.nix +++ /dev/null @@ -1,66 +0,0 @@ -# Copyright 2022-2024 TII (SSRC) and the Ghaf contributors -# SPDX-License-Identifier: Apache-2.0 -{ config, lib, ... }: -# account for the development time login with sudo rights -let - cfg = config.ghaf.users.accounts; - inherit (lib) - mkEnableOption - mkOption - optionals - mkIf - types - ; -in -{ - #TODO Extend this to allow definition of multiple users - options.ghaf.users.accounts = { - enable = mkEnableOption "Default account Setup"; - user = mkOption { - default = "ghaf"; - type = with types; str; - description = '' - A default user to create in the system. - ''; - }; - uid = mkOption { - default = 1000; - type = with types; int; - description = '' - A default user id for the user. - ''; - }; - password = mkOption { - default = "ghaf"; - type = with types; str; - description = '' - A default password for the user. - ''; - }; - }; - - config = mkIf cfg.enable { - users = { - mutableUsers = false; - users."${cfg.user}" = { - isNormalUser = true; - inherit (cfg) password; - inherit (cfg) uid; - #TODO add "docker" use "lib.optionals" - extraGroups = [ - "wheel" - "video" - "networkmanager" - ] ++ optionals config.security.tpm2.enable [ "tss" ]; - }; - groups."${cfg.user}" = { - name = cfg.user; - members = [ cfg.user ]; - }; - }; - - # to build ghaf as ghaf-user with caches - nix.settings.trusted-users = mkIf config.ghaf.profiles.debug.enable [ cfg.user ]; - #services.userborn.enable = true; - }; -} diff --git a/modules/common/users/admin.nix b/modules/common/users/admin.nix new file mode 100644 index 000000000..e03e2c32d --- /dev/null +++ b/modules/common/users/admin.nix @@ -0,0 +1,82 @@ +# Copyright 2022-2024 TII (SSRC) and the Ghaf contributors +# SPDX-License-Identifier: Apache-2.0 +{ + config, + lib, + ... +}: +let + cfg = config.ghaf.users.admin; + inherit (lib) + mkIf + types + mkOption + optionals + ; +in +{ + options.ghaf.users.admin = { + enable = mkOption { + description = "Enable the admin user account. Enabled by default."; + type = types.bool; + default = true; + }; + name = mkOption { + description = "Admin account name."; + type = types.str; + default = "ghaf"; + }; + initialPassword = mkOption { + description = "Default password for the admin user account."; + type = types.str; + default = "ghaf"; + }; + initialHashedPassword = mkOption { + description = "Initial hashed password for the admin user account."; + type = types.nullOr types.str; + default = null; + }; + hashedPassword = mkOption { + description = "Hashed password for live updates."; + type = types.nullOr types.str; + default = null; + }; + extraGroups = mkOption { + description = "Extra groups for the admin user."; + type = types.listOf types.str; + default = [ ]; + }; + }; + + config = mkIf cfg.enable { + + users = { + users = { + "${cfg.name}" = { + isNormalUser = true; + inherit (cfg) initialPassword; + inherit (cfg) initialHashedPassword; + inherit (cfg) hashedPassword; + home = "/tmp/${cfg.name}"; + extraGroups = + [ + "wheel" + "video" + ] + ++ cfg.extraGroups + ++ optionals config.security.tpm2.enable [ "tss" ] + ++ optionals config.ghaf.virtualization.docker.daemon.enable [ "docker" ]; + }; + }; + groups = { + "${cfg.name}" = { + inherit (cfg) name; + members = [ cfg.name ]; + }; + }; + }; + + # to build ghaf as admin with caches + nix.settings.trusted-users = mkIf config.ghaf.profiles.debug.enable [ cfg.name ]; + }; +} diff --git a/modules/common/users/common.nix b/modules/common/users/common.nix new file mode 100644 index 000000000..65fbc96c9 --- /dev/null +++ b/modules/common/users/common.nix @@ -0,0 +1,26 @@ +# Copyright 2022-2024 TII (SSRC) and the Ghaf contributors +# SPDX-License-Identifier: Apache-2.0 +{ + config, + lib, + ... +}: +let + inherit (lib) mkDefault hasAttr; + hasStorageVm = (hasAttr "storagevm" config.ghaf) && config.ghaf.storagevm.enable; +in +{ + # Common ghaf user settings + config = { + + # Disable mutable users + users.mutableUsers = mkDefault false; + + # Enable userborn + services.userborn = { + enable = mkDefault true; + passwordFilesLocation = if hasStorageVm then "/var/lib/nixos" else "/etc"; + }; + + }; +} diff --git a/modules/common/users/default.nix b/modules/common/users/default.nix new file mode 100644 index 000000000..5338bf42c --- /dev/null +++ b/modules/common/users/default.nix @@ -0,0 +1,9 @@ +# Copyright 2024 TII (SSRC) and the Ghaf contributors +# SPDX-License-Identifier: Apache-2.0 +{ + imports = [ + ./common.nix + ./admin.nix + ./desktop.nix + ]; +} diff --git a/modules/common/users/desktop.nix b/modules/common/users/desktop.nix new file mode 100644 index 000000000..c635916c1 --- /dev/null +++ b/modules/common/users/desktop.nix @@ -0,0 +1,248 @@ +# Copyright 2022-2024 TII (SSRC) and the Ghaf contributors +# SPDX-License-Identifier: Apache-2.0 +{ + config, + lib, + pkgs, + ... +}: +let + cfg = config.ghaf.users; + inherit (lib) + mkIf + types + mkOption + mkMerge + optionalString + concatStringsSep + ; + + loginUserAccount = types.submodule { + options = { + enable = mkOption { + description = "Enable desktop login user account."; + type = types.bool; + default = false; + }; + uid = mkOption { + description = '' + Login user identifier (uid). Defaults to 1001. + This UID is also used by the proxy and app auxiliary users. + ''; + type = types.int; + default = 1001; + }; + extraGroups = mkOption { + description = "Extra groups for the login user."; + type = types.listOf types.str; + default = [ ]; + }; + homeSize = mkOption { + description = '' + Size of the home directory for the login user in MB (integer). + The integer size is inherited from the microvm volume size parameter. + ''; + type = types.int; + default = 10000; + }; + }; + }; + + auxiliaryAccount = types.submodule { + options = { + enable = mkOption { + description = "Enable auxiliary user account."; + type = types.bool; + default = false; + }; + name = mkOption { + description = "Auxiliary user's name."; + type = types.str; + }; + extraGroups = mkOption { + description = "Extra groups for the auxiliary user."; + type = types.listOf types.str; + default = [ ]; + }; + }; + }; + +in +{ + options.ghaf.users = { + # Main UI user + loginUser = mkOption { + description = "User account for desktop login."; + type = loginUserAccount; + default = { }; + }; + # Proxy user for dbus + proxyUser = mkOption { + description = "User account for dbus proxy functionality."; + type = auxiliaryAccount; + }; + # App user for running applications + appUser = mkOption { + description = "User account to run applications."; + type = auxiliaryAccount; + }; + }; + + config = mkMerge [ + { + assertions = [ + { + assertion = cfg.loginUser.enable -> config.ghaf.systemd.withHomed; + message = "You cannot enable login user without systemd-homed. Enable homed service in systemd module."; + } + { + assertion = cfg.loginUser.enable -> !cfg.proxyUser.enable; + message = "You cannot enable both login and proxy users at the same time."; + } + { + assertion = cfg.loginUser.enable -> !cfg.appUser.enable; + message = "You cannot enable both login and app users at the same time."; + } + ]; + + # Hardcode auxiliary user names + ghaf.users.appUser.name = "appuser"; + ghaf.users.proxyUser.name = "proxyuser"; + + users = { + users = mkMerge [ + (mkIf cfg.proxyUser.enable { + "${cfg.proxyUser.name}" = { + isNormalUser = true; + createHome = false; + inherit (cfg.loginUser) uid; + inherit (cfg.proxyUser) extraGroups; + }; + }) + (mkIf cfg.appUser.enable { + "${cfg.appUser.name}" = { + isNormalUser = true; + createHome = true; + inherit (cfg.loginUser) uid; + inherit (cfg.appUser) extraGroups; + }; + }) + ]; + groups = mkMerge [ + (mkIf cfg.proxyUser.enable { + "${cfg.proxyUser.name}" = { + inherit (cfg.proxyUser) name; + members = [ cfg.proxyUser.name ]; + }; + }) + (mkIf cfg.appUser.enable { + "${cfg.appUser.name}" = { + inherit (cfg.appUser) name; + members = [ cfg.appUser.name ]; + }; + }) + ]; + }; + } + + # Login user setup with homed + (mkIf cfg.loginUser.enable { + + # Enable homed service + services.homed.enable = true; + + # First boot login user setup service + systemd.services.ghaf-loginuser-setup = + let + userSetupScript = pkgs.writeShellApplication { + name = "ghaf-user-setup"; + runtimeInputs = [ + pkgs.coreutils + pkgs.ncurses + pkgs.brightnessctl + ]; + text = '' + brightnessctl set 100% + clear + echo -e "\e[1;32;1mWelcome to Ghaf \e[0m" + echo "" + echo "Start by creating your user account." + echo "" + + # Read new user name + ACCEPTABLE_USER=false + until $ACCEPTABLE_USER; do + echo -n "Enter your user name: " + read -e -r USERNAME + USERNAME=''${USERNAME//_/} + USERNAME=''${USERNAME// /_} + USERNAME=''${USERNAME//[^a-zA-Z0-9_]/} + USERNAME=''$(echo -n "$USERNAME" | tr '[:upper:]' '[:lower:]') + if grep -q -w "$USERNAME:" /etc/passwd; then + echo "User $USERNAME already exists. Please choose another user name." + else + ACCEPTABLE_USER=true + fi + done + + echo "" + echo -n "Enter your full name: " + read -e -r REALNAME + REALNAME=''${REALNAME//[^a-zA-Z ]/} + [[ -n "$REALNAME" ]] || REALNAME="$USERNAME"; + + echo "" + echo "Setting up your user account and creating encrypted home folder after you enter your password." + echo "This may take a while..." + echo "" + + # Add login user and home + homectl create "$USERNAME" \ + --real-name="$REALNAME" \ + --skel=/etc/skel \ + --storage=luks \ + --luks-pbkdf-type=argon2id \ + --enforce-password-policy=true \ + --drop-caches=true \ + --nosuid=true \ + --noexec=true \ + --nodev=true \ + --disk-size=${toString cfg.loginUser.homeSize}M \ + --shell=/run/current-system/sw/bin/bash \ + --uid=${toString cfg.loginUser.uid} \ + --member-of=users${ + optionalString ( + cfg.loginUser.extraGroups != [ ] + ) ",${concatStringsSep "," cfg.loginUser.extraGroups}" + } + + # Lock user creation script + install -m 000 /dev/null /var/lib/nixos/user.lock + + echo "" + echo "User $USERNAME created. Starting user session..." + sleep 1 + ''; + }; + in + { + description = "First boot user setup"; + enable = true; + requiredBy = [ "multi-user.target" ]; + before = [ "systemd-user-sessions.service" ]; + path = [ userSetupScript ]; + unitConfig.ConditionPathExists = "!/var/lib/nixos/user.lock"; + serviceConfig = { + Type = "oneshot"; + StandardInput = "tty"; + StandardOutput = "tty"; + StandardError = "journal"; + TTYPath = "/dev/tty1"; + TTYReset = true; + TTYVHangup = true; + ExecStart = "${userSetupScript}/bin/ghaf-user-setup"; + }; + }; + }) + ]; +} diff --git a/modules/common/users/other.nix b/modules/common/users/other.nix new file mode 100644 index 000000000..db2848ab4 --- /dev/null +++ b/modules/common/users/other.nix @@ -0,0 +1,111 @@ +# Copyright 2022-2024 TII (SSRC) and the Ghaf contributors +# SPDX-License-Identifier: Apache-2.0 +{ + config, + lib, + ... +}: +let + cfg = config.ghaf.users; + inherit (lib) + mkIf + types + mkOption + ; + + userAccount = types.submodule { + options = { + enable = mkOption { + description = "Enable user account"; + type = types.bool; + default = false; + }; + name = mkOption { + description = "User name"; + type = types.str; + default = ""; + }; + initialPassword = mkOption { + description = "Default password for the admin user account."; + type = types.str; + default = "ghaf"; + }; + initialHashedPassword = mkOption { + description = "Initial hashed password for the admin user account."; + type = types.nullOr types.str; + default = null; + }; + hashedPassword = mkOption { + description = "Hashed password for live updates."; + type = types.nullOr types.str; + default = null; + }; + uid = mkOption { + description = "Optional user identifier (uid). Defaults to null."; + type = types.nullOr types.int; + default = null; + }; + gid = mkOption { + description = "Optional primary group identifier (gid). Defaults to null."; + type = types.nullOr types.int; + default = null; + }; + extraGroups = mkOption { + description = "Extra groups for the user."; + type = types.listOf types.str; + default = [ ]; + }; + }; + }; + +in +{ + options.ghaf.users = { + managed = mkOption { + description = '' + List of declarativively managed user accounts. + + The ghaf user interface for declarative users has the following options: + - enable: Enable user account + - name: User name + - initialPassword: Default password for the user account. + - initialHashedPassword: Initial hashed password for the user account. + - hashedPassword: Hashed password for live updates. + - uid: Optional user identifier (uid). Defaults to null. + - gid: Optional primary group identifier (gid). Defaults to null. + - extraGroups: Extra groups for the user. + + Additional user options may be handled through the NixOS user module. + ''; + type = types.listOf userAccount; + default = [ ]; + }; + }; + + config = { + users = { + users = { + "${cfg.name}" = + { + isNormalUser = true; + inherit (cfg) initialPassword; + inherit (cfg) initialHashedPassword; + inherit (cfg) hashedPassword; + inherit (cfg) extraGroups; + } + // lib.optionalAttrs (cfg.uid != null) { + inherit (cfg) uid; + } + // lib.optionalAttrs (cfg.gid != null) { + inherit (cfg) gid; + }; + groups = mkIf (cfg.gid == null) { + "${cfg.name}" = { + inherit (cfg) name; + members = [ cfg.name ]; + }; + }; + }; + }; + }; +} diff --git a/modules/desktop/graphics/labwc.config.nix b/modules/desktop/graphics/labwc.config.nix index 5848ba02c..85151cec6 100644 --- a/modules/desktop/graphics/labwc.config.nix +++ b/modules/desktop/graphics/labwc.config.nix @@ -342,7 +342,7 @@ in services.greetd.settings = { initial_session = lib.mkIf (cfg.autologinUser != null) { - user = "ghaf"; + user = config.ghaf.users.admin.name; command = "ghaf-session"; }; }; diff --git a/modules/desktop/graphics/labwc.nix b/modules/desktop/graphics/labwc.nix index 02402bc20..c228686be 100644 --- a/modules/desktop/graphics/labwc.nix +++ b/modules/desktop/graphics/labwc.nix @@ -26,7 +26,7 @@ in }; autologinUser = lib.mkOption { type = lib.types.nullOr lib.types.str; - default = config.ghaf.users.accounts.user; + default = config.ghaf.users.admin.name; description = '' Username of the account that will be automatically logged in to the desktop. If unspecified, the login manager is shown as usual. diff --git a/modules/givc/appvm.nix b/modules/givc/appvm.nix index f0e698e27..c15e6a001 100644 --- a/modules/givc/appvm.nix +++ b/modules/givc/appvm.nix @@ -47,7 +47,7 @@ in admin = config.ghaf.givc.adminConfig; }; - # Quick fix to allow linger (linger option in user def. currently doesn't work, e.g., bc mutable) - systemd.tmpfiles.rules = [ "f /var/lib/systemd/linger/${config.ghaf.users.accounts.user}" ]; + # Enable lingering + users.users.${config.ghaf.users.appUser.name}.linger = true; }; } diff --git a/modules/givc/audiovm.nix b/modules/givc/audiovm.nix index a048f5c26..9accd06bc 100644 --- a/modules/givc/audiovm.nix +++ b/modules/givc/audiovm.nix @@ -42,8 +42,7 @@ in enable = true; system = { enable = true; - # TODO Change this with new user setup - user = "ghaf"; + user = config.ghaf.users.proxyUser.name; socket = "/tmp/dbusproxy_snd.sock"; policy = { talk = [ diff --git a/modules/givc/common.nix b/modules/givc/common.nix index a7b929f46..0b8e36f24 100644 --- a/modules/givc/common.nix +++ b/modules/givc/common.nix @@ -12,7 +12,7 @@ let mitmEnabled = config.ghaf.virtualization.microvm.idsvm.enable && config.ghaf.virtualization.microvm.idsvm.mitmproxy.enable; - mitmExtraArgs = lib.optionalString mitmEnabled "--user-data-dir=/home/${config.ghaf.users.accounts.user}/.config/google-chrome/Default --test-type --ignore-certificate-errors-spki-list=Bq49YmAq1CG6FuBzp8nsyRXumW7Dmkp7QQ/F82azxGU="; + mitmExtraArgs = lib.optionalString mitmEnabled "--user-data-dir=/home/${config.ghaf.users.appUser.name}/.config/google-chrome/Default --test-type --ignore-certificate-errors-spki-list=Bq49YmAq1CG6FuBzp8nsyRXumW7Dmkp7QQ/F82azxGU="; in { options.ghaf.givc = { diff --git a/modules/givc/netvm.nix b/modules/givc/netvm.nix index a1a7cf1d1..b760e76ec 100644 --- a/modules/givc/netvm.nix +++ b/modules/givc/netvm.nix @@ -50,8 +50,7 @@ in enable = true; system = { enable = true; - # TODO Change this with new user setup - user = "ghaf"; + user = config.ghaf.users.proxyUser.name; socket = "/tmp/dbusproxy_net.sock"; policy = { own = [ diff --git a/modules/hardware/common/shared-mem.nix b/modules/hardware/common/shared-mem.nix index 222894975..c8c7520dd 100644 --- a/modules/hardware/common/shared-mem.nix +++ b/modules/hardware/common/shared-mem.nix @@ -34,7 +34,7 @@ in type = types.int; default = 16; description = mdDoc '' - Specifies the size of the shared memory region, measured in + Specifies the size of the shared memory region, measured in megabytes (MB) ''; }; @@ -42,7 +42,7 @@ in type = types.str; default = "2M"; description = mdDoc '' - Specifies the size of the large memory page area. Supported kernel + Specifies the size of the large memory page area. Supported kernel values are 2 MB and 1 GB ''; apply = @@ -56,7 +56,7 @@ in type = types.path; default = "/tmp/ivshmem_socket"; # The value is hardcoded in the application description = mdDoc '' - Specifies the path to the shared memory socket, used by QEMU + Specifies the path to the shared memory socket, used by QEMU instances for inter-VM memory sharing and interrupt signaling ''; }; @@ -65,7 +65,7 @@ in default = "0x920000000"; description = mdDoc '' Maps the shared memory to a physical address if set to a non-zero value. - The address must be platform-specific and arbitrarily chosen to avoid + The address must be platform-specific and arbitrarily chosen to avoid conflicts with other memory areas, such as PCI regions. ''; }; @@ -93,19 +93,19 @@ in }; serverSocketPath = mkOption { type = types.path; - default = "/run/user/${builtins.toString config.ghaf.users.accounts.uid}/memsocket-server.sock"; + default = "/run/user/${builtins.toString config.ghaf.users.loginUser.uid}/memsocket-server.sock"; description = mdDoc '' - Specifies the path of the listening socket, which is used by Waypipe - or other server applications as the output socket in server mode for + Specifies the path of the listening socket, which is used by Waypipe + or other server applications as the output socket in server mode for data transmission ''; }; clientSocketPath = mkOption { type = types.path; - default = "/run/user/${builtins.toString config.ghaf.users.accounts.uid}/memsocket-client.sock"; + default = "/run/user/${builtins.toString config.ghaf.users.loginUser.uid}/memsocket-client.sock"; description = mdDoc '' - Specifies the location of the output socket, which will connected to - in order to receive data from AppVMs. This socket must be created by + Specifies the location of the output socket, which will connected to + in order to receive data from AppVMs. This socket must be created by another application, such as Waypipe, when operating in client mode ''; }; @@ -113,8 +113,8 @@ in type = types.bool; default = false; description = mdDoc '' - Enables the use of shared memory with Waypipe for Wayland-enabled - applications running on virtual machines (VMs), facilitating + Enables the use of shared memory with Waypipe for Wayland-enabled + applications running on virtual machines (VMs), facilitating efficient inter-VM communication ''; }; diff --git a/modules/microvm/virtualization/microvm/adminvm.nix b/modules/microvm/virtualization/microvm/adminvm.nix index 9e235a9ca..8fdee3b58 100644 --- a/modules/microvm/virtualization/microvm/adminvm.nix +++ b/modules/microvm/virtualization/microvm/adminvm.nix @@ -21,6 +21,7 @@ let ; internalIP = 10; }) + ./common/storagevm.nix # We need to retrieve mac address and start log aggregator ../../../common/logging/hw-mac-retrieve.nix ../../../common/logging/logs-aggregator.nix @@ -29,7 +30,7 @@ let { lib, ... }: { ghaf = { - users.accounts.enable = lib.mkDefault configHost.ghaf.users.accounts.enable; + # Profiles profiles.debug.enable = lib.mkDefault configHost.ghaf.profiles.debug.enable; development = { # NOTE: SSH port also becomes accessible on the network interface @@ -38,6 +39,8 @@ let debug.tools.enable = lib.mkDefault configHost.ghaf.development.debug.tools.enable; nix-setup.enable = lib.mkDefault configHost.ghaf.development.nix-setup.enable; }; + + # System systemd = { enable = true; withName = "adminvm-systemd"; @@ -49,18 +52,19 @@ let withDebug = configHost.ghaf.profiles.debug.enable; withHardenedConfigs = true; }; + givc.adminvm.enable = true; + + # Storage storagevm = { enable = true; - name = "adminvm"; + name = vmName; files = [ "/etc/locale-givc.conf" "/etc/timezone.conf" ]; }; - givc.adminvm.enable = true; - - # Log aggregation configuration + # Services logging = { client.enable = isLoggingEnabled; listener = { diff --git a/modules/microvm/virtualization/microvm/appvm.nix b/modules/microvm/virtualization/microvm/appvm.nix index 6e912a174..5e2b77d45 100644 --- a/modules/microvm/virtualization/microvm/appvm.nix +++ b/modules/microvm/virtualization/microvm/appvm.nix @@ -77,14 +77,17 @@ let }: { ghaf = { - users.accounts.enable = lib.mkDefault configHost.ghaf.users.accounts.enable; - profiles.debug.enable = lib.mkDefault configHost.ghaf.profiles.debug.enable; + # Profiles + users.appUser.enable = true; + profiles.debug.enable = lib.mkDefault configHost.ghaf.profiles.debug.enable; development = { ssh.daemon.enable = lib.mkDefault configHost.ghaf.development.ssh.daemon.enable; debug.tools.enable = lib.mkDefault configHost.ghaf.development.debug.tools.enable; nix-setup.enable = lib.mkDefault configHost.ghaf.development.nix-setup.enable; }; + + # Systemd systemd = { enable = true; withName = "appvm-systemd"; @@ -106,8 +109,8 @@ let storagevm = { enable = true; - name = "${vm.name}"; - users.${config.ghaf.users.accounts.user}.directories = [ + name = vmName; + users.${config.ghaf.users.appUser.name}.directories = [ ".config/" "Downloads" "Music" @@ -130,7 +133,9 @@ let # setting mode), instead of symlinking it. environment.etc.${configHost.ghaf.security.sshKeys.getAuthKeysFilePathInEtc} = sshKeysHelper.getAuthKeysSource; - services.openssh = configHost.ghaf.security.sshKeys.sshAuthorizedKeysCommand; + services.openssh = configHost.ghaf.security.sshKeys.sshAuthorizedKeysCommand // { + authorizedKeysCommandUser = config.ghaf.users.appUser.name; + }; system.stateVersion = lib.trivial.release; diff --git a/modules/microvm/virtualization/microvm/audiovm.nix b/modules/microvm/virtualization/microvm/audiovm.nix index 8918fd6b6..cefb803df 100644 --- a/modules/microvm/virtualization/microvm/audiovm.nix +++ b/modules/microvm/virtualization/microvm/audiovm.nix @@ -4,21 +4,14 @@ { config, lib, - pkgs, ... }: let configHost = config; vmName = "audio-vm"; macAddress = "02:00:00:03:03:03"; - isGuiVmEnabled = config.ghaf.virtualization.microvm.guivm.enable; has_acpi_path = config.ghaf.hardware.definition.audio.acpiPath != null; - sshKeysHelper = pkgs.callPackage ../../../../packages/ssh-keys-helper { - inherit pkgs; - inherit config; - }; - audiovmBaseConfiguration = { imports = [ inputs.self.nixosModules.givc-audiovm @@ -40,14 +33,23 @@ let imports = [ ../../../common ]; ghaf = { - users.accounts.enable = lib.mkDefault configHost.ghaf.users.accounts.enable; + # Profiles profiles.debug.enable = lib.mkDefault configHost.ghaf.profiles.debug.enable; - development = { ssh.daemon.enable = lib.mkDefault configHost.ghaf.development.ssh.daemon.enable; debug.tools.enable = lib.mkDefault configHost.ghaf.development.debug.tools.enable; nix-setup.enable = lib.mkDefault configHost.ghaf.development.nix-setup.enable; }; + users.proxyUser = { + enable = true; + extraGroups = [ + "audio" + "video" + "pipewire" + ]; + }; + + # System systemd = { enable = true; withName = "audiovm-systemd"; @@ -61,14 +63,18 @@ let withHardenedConfigs = true; }; givc.audiovm.enable = true; + + # Storage + storagevm = { + enable = true; + name = vmName; + }; + + # Services services.audio.enable = true; # Logging client configuration logging.client.enable = configHost.ghaf.logging.client.enable; logging.client.endpoint = configHost.ghaf.logging.client.endpoint; - storagevm = { - enable = true; - name = "audiovm"; - }; }; environment = { @@ -87,32 +93,20 @@ let hostPlatform.system = configHost.nixpkgs.hostPlatform.system; }; - services.openssh = config.ghaf.security.sshKeys.sshAuthorizedKeysCommand; - microvm = { # Optimize is disabled because when it is enabled, qemu is built without libusb optimize.enable = false; vcpu = 2; mem = 384; hypervisor = "qemu"; - shares = - [ - { - tag = "ro-store"; - source = "/nix/store"; - mountPoint = "/nix/.ro-store"; - proto = "virtiofs"; - } - ] - ++ lib.optionals isGuiVmEnabled [ - { - # Add the waypipe-ssh public key to the microvm - tag = config.ghaf.security.sshKeys.waypipeSshPublicKeyName; - source = config.ghaf.security.sshKeys.waypipeSshPublicKeyDir; - mountPoint = config.ghaf.security.sshKeys.waypipeSshPublicKeyDir; - proto = "virtiofs"; - } - ]; + shares = [ + { + tag = "ro-store"; + source = "/nix/store"; + mountPoint = "/nix/.ro-store"; + proto = "virtiofs"; + } + ]; writableStoreOverlay = lib.mkIf config.ghaf.development.debug.tools.enable "/nix/.rw-store"; qemu = { machine = @@ -133,18 +127,6 @@ let ]; }; }; - - fileSystems = lib.mkIf isGuiVmEnabled { - ${config.ghaf.security.sshKeys.waypipeSshPublicKeyDir}.options = [ "ro" ]; - }; - - # SSH is very picky about to file permissions and ownership and will - # accept neither direct path inside /nix/store or symlink that points - # there. Therefore we copy the file to /etc/ssh/get-auth-keys (by - # setting mode), instead of symlinking it. - environment.etc = lib.mkIf isGuiVmEnabled { - ${config.ghaf.security.sshKeys.getAuthKeysFilePathInEtc} = sshKeysHelper.getAuthKeysSource; - }; } ) ]; diff --git a/modules/microvm/virtualization/microvm/common/shared-directory.nix b/modules/microvm/virtualization/microvm/common/shared-directory.nix index 80db4d668..bb995d549 100644 --- a/modules/microvm/virtualization/microvm/common/shared-directory.nix +++ b/modules/microvm/virtualization/microvm/common/shared-directory.nix @@ -4,10 +4,9 @@ name: { lib, config, ... }: let cfg = config.ghaf.storagevm; - shared-mountPath = "/tmp/shared/shares"; - inherit (config.ghaf.users.accounts) user; isGuiVm = builtins.stringLength name == 0; - userDir = "/home/${user}" + (if isGuiVm then "/Shares" else "/Unsafe\ share"); + shared-mountPath = "/tmp/shared/shares"; + userDir = if isGuiVm then "/Shares" else "/home/${config.ghaf.users.appUser.name}/Unsafe\ share"; in { config = lib.mkIf cfg.enable { @@ -37,5 +36,12 @@ in "x-gvfs-hide" ]; }; + + # Add bookmark to skel + environment.etc = lib.mkIf config.ghaf.users.loginUser.enable { + "skel/.gtk-bookmarks".text = '' + file:///Shares Shares + ''; + }; }; } diff --git a/modules/microvm/virtualization/microvm/common/storagevm.nix b/modules/microvm/virtualization/microvm/common/storagevm.nix index 15e3b84e0..d39698156 100644 --- a/modules/microvm/virtualization/microvm/common/storagevm.nix +++ b/modules/microvm/virtualization/microvm/common/storagevm.nix @@ -1,12 +1,22 @@ # Copyright 2022-2024 TII (SSRC) and the Ghaf contributors # SPDX-License-Identifier: Apache-2.0 -{ lib, config, ... }: +{ + lib, + config, + ... +}: let cfg = config.ghaf.storagevm; - mountPath = "/guestStorage"; + inherit (lib) + mkEnableOption + mkOption + mkIf + mkMerge + types + ; in { - options.ghaf.storagevm = with lib; { + options.ghaf.storagevm = { enable = mkEnableOption "StorageVM support"; name = mkOption { @@ -16,6 +26,14 @@ in type = types.str; }; + mountPath = mkOption { + description = '' + Mount path for the storage virtual machine. + ''; + type = types.str; + default = "/guestStorage"; + }; + directories = mkOption { # FIXME: Probably will lead to disgraceful error messages, as we # put typechecking on nix impermanence option. But other, @@ -61,7 +79,7 @@ in }; config = lib.mkIf cfg.enable { - fileSystems.${mountPath} = { + fileSystems.${cfg.mountPath} = { neededForBoot = true; options = [ "rw" @@ -70,7 +88,7 @@ in "noexec" ]; }; - virtualisation.fileSystems.${mountPath}.device = "/dev/vda"; + virtualisation.fileSystems.${cfg.mountPath}.device = "/dev/vda"; microvm.shares = [ { @@ -78,23 +96,41 @@ in proto = "virtiofs"; securityModel = "passthrough"; source = "/storagevm/${cfg.name}"; - mountPoint = mountPath; + mountPoint = cfg.mountPath; } ]; - environment.persistence.${mountPath} = lib.mkMerge [ + microvm.volumes = lib.optionals config.ghaf.users.loginUser.enable [ + { + image = "/storagevm/homes/${cfg.name}-home.img"; + size = builtins.floor (config.ghaf.users.loginUser.homeSize * 1.15); + fsType = "btrfs"; + mountPoint = "/home"; + } + ]; + + environment.persistence.${cfg.mountPath} = mkMerge [ { hideMounts = true; directories = [ "/var/lib/nixos" ]; - files = [ "/etc/ssh/ssh_host_ed25519_key.pub" "/etc/ssh/ssh_host_ed25519_key" ]; } { inherit (cfg) directories users files; } + (mkIf config.ghaf.users.loginUser.enable { + directories = [ + "/var/lib/systemd/home" + ]; + }) ]; + + # Workaround, fixes homed machine-id dependency + environment.etc = lib.optionalAttrs config.ghaf.users.loginUser.enable { + machine-id.text = "d8dee68f8d334c79ac8f8229921e0b25"; + }; }; } diff --git a/modules/microvm/virtualization/microvm/guivm.nix b/modules/microvm/virtualization/microvm/guivm.nix index 9dd1759d5..468a885e0 100644 --- a/modules/microvm/virtualization/microvm/guivm.nix +++ b/modules/microvm/virtualization/microvm/guivm.nix @@ -66,24 +66,17 @@ let in { ghaf = { - users.accounts.enable = lib.mkDefault config.ghaf.users.accounts.enable; + # Profiles profiles = { debug.enable = lib.mkDefault config.ghaf.profiles.debug.enable; applications.enable = false; graphics.enable = true; }; - - # Create launchers for regular apps running in the GUIVM and virtualized ones if GIVC is enabled - graphics.launchers = guivmLaunchers ++ lib.optionals config.ghaf.givc.enable virtualLaunchers; - - # To enable screen locking set to true - graphics.labwc = { - autolock.enable = lib.mkDefault config.ghaf.graphics.labwc.autolock.enable; - autologinUser = lib.mkDefault config.ghaf.graphics.labwc.autologinUser; - securityContext = map (vm: { - identifier = vm.name; - color = vm.borderColor; - }) config.ghaf.virtualization.microvm.appvm.vms; + users = { + loginUser = { + enable = true; + extraGroups = [ "video" ]; + }; }; development = { @@ -91,40 +84,42 @@ let debug.tools.enable = lib.mkDefault config.ghaf.development.debug.tools.enable; nix-setup.enable = lib.mkDefault config.ghaf.development.nix-setup.enable; }; + + # System systemd = { enable = true; withName = "guivm-systemd"; withAudit = config.ghaf.profiles.debug.enable; + withHomed = true; withLocaled = true; withNss = true; withResolved = true; withTimesyncd = true; withDebug = config.ghaf.profiles.debug.enable; - withHardenedConfigs = true; + withHardenedConfigs = false; }; givc.guivm.enable = true; - # Logging client configuration - logging.client.enable = config.ghaf.logging.client.enable; - logging.client.endpoint = config.ghaf.logging.client.endpoint; + + # Storage storagevm = { enable = true; - name = "guivm"; - directories = [ - { - directory = "/var/lib/private/ollama"; - inherit (config.ghaf.users.accounts) user; - group = "ollama"; - mode = "u=rwx,g=,o="; - } - ]; - users.${config.ghaf.users.accounts.user}.directories = [ - ".cache" - ".config" - ".local" - "Pictures" - "Videos" - ]; + name = vmName; + }; + + # Services + + # Create launchers for regular apps running in the GUIVM and virtualized ones if GIVC is enabled + graphics.launchers = guivmLaunchers ++ lib.optionals config.ghaf.givc.enable virtualLaunchers; + graphics.labwc = { + autolock.enable = lib.mkDefault config.ghaf.graphics.labwc.autolock.enable; + autologinUser = lib.mkDefault config.ghaf.graphics.labwc.autologinUser; + securityContext = map (vm: { + identifier = vm.name; + color = vm.borderColor; + }) config.ghaf.virtualization.microvm.appvm.vms; }; + logging.client.enable = config.ghaf.logging.client.enable; + logging.client.endpoint = config.ghaf.logging.client.endpoint; services.disks.enable = true; services.disks.fileManager = "${pkgs.pcmanfm}/bin/pcmanfm"; services.xdghandlers.enable = true; @@ -141,7 +136,7 @@ let # Switch off display, if wayland is running if ${pkgs.procps}/bin/pgrep -fl "wayland" > /dev/null; then wl_running=1 - WAYLAND_DISPLAY=/run/user/${builtins.toString config.ghaf.users.accounts.uid}/wayland-0 ${pkgs.wlopm}/bin/wlopm --off '*' + WAYLAND_DISPLAY=/run/user/${builtins.toString config.ghaf.users.loginUser.uid}/wayland-0 ${pkgs.wlopm}/bin/wlopm --off '*' else wl_running=0 fi @@ -151,7 +146,7 @@ let # Enable display if [ "$wl_running" -eq 1 ]; then - WAYLAND_DISPLAY=/run/user/${builtins.toString config.ghaf.users.accounts.uid}/wayland-0 ${pkgs.wlopm}/bin/wlopm --on '*' + WAYLAND_DISPLAY=/run/user/${builtins.toString config.ghaf.users.loginUser.uid}/wayland-0 ${pkgs.wlopm}/bin/wlopm --on '*' fi ;; "button/lid LID open") @@ -163,12 +158,15 @@ let systemd.services."waypipe-ssh-keygen" = let + uid = "${toString config.ghaf.users.loginUser.uid}"; + pubDir = config.ghaf.security.sshKeys.waypipeSshPublicKeyDir; keygenScript = pkgs.writeShellScriptBin "waypipe-ssh-keygen" '' set -xeuo pipefail mkdir -p /run/waypipe-ssh echo -en "\n\n\n" | ${pkgs.openssh}/bin/ssh-keygen -t ed25519 -f /run/waypipe-ssh/id_ed25519 -C "" - chown ghaf:ghaf /run/waypipe-ssh/* - cp /run/waypipe-ssh/id_ed25519.pub /run/waypipe-ssh-public-key/id_ed25519.pub + chown ${uid}:users /run/waypipe-ssh/* + cp /run/waypipe-ssh/id_ed25519.pub ${pubDir}/id_ed25519.pub + chown -R ${uid}:users ${pubDir} ''; in { @@ -234,7 +232,7 @@ let hypervisor = "qemu"; shares = [ { - tag = "rw-waypipe-ssh-public-key"; + tag = "waypipe-ssh-public-key"; source = config.ghaf.security.sshKeys.waypipeSshPublicKeyDir; mountPoint = config.ghaf.security.sshKeys.waypipeSshPublicKeyDir; proto = "virtiofs"; @@ -382,27 +380,5 @@ in imports = guivmBaseConfiguration.imports ++ cfg.extraModules; }; }; - - # This directory needs to be created before any of the microvms start. - systemd.services."create-waypipe-ssh-public-key-directory" = - let - script = pkgs.writeShellScriptBin "create-waypipe-ssh-public-key-directory" '' - mkdir -pv ${config.ghaf.security.sshKeys.waypipeSshPublicKeyDir} - chown -v microvm ${config.ghaf.security.sshKeys.waypipeSshPublicKeyDir} - ''; - in - { - enable = true; - description = "Create shared directory on host"; - path = [ ]; - wantedBy = [ "microvms.target" ]; - serviceConfig = { - Type = "oneshot"; - RemainAfterExit = true; - StandardOutput = "journal"; - StandardError = "journal"; - ExecStart = "${script}/bin/create-waypipe-ssh-public-key-directory"; - }; - }; }; } diff --git a/modules/microvm/virtualization/microvm/idsvm/idsvm.nix b/modules/microvm/virtualization/microvm/idsvm/idsvm.nix index 4354e26c0..ab868988a 100644 --- a/modules/microvm/virtualization/microvm/idsvm/idsvm.nix +++ b/modules/microvm/virtualization/microvm/idsvm/idsvm.nix @@ -25,7 +25,6 @@ let { lib, ... }: { ghaf = { - users.accounts.enable = lib.mkDefault configHost.ghaf.users.accounts.enable; profiles.debug.enable = lib.mkDefault configHost.ghaf.profiles.debug.enable; virtualization.microvm.idsvm.mitmproxy.enable = diff --git a/modules/microvm/virtualization/microvm/microvm-host.nix b/modules/microvm/virtualization/microvm/microvm-host.nix index e0a129a43..e7e30203b 100644 --- a/modules/microvm/virtualization/microvm/microvm-host.nix +++ b/modules/microvm/virtualization/microvm/microvm-host.nix @@ -9,6 +9,14 @@ }: let cfg = config.ghaf.virtualization.microvm-host; + inherit (lib) + mkEnableOption + mkOption + mkIf + mkMerge + types + ; + has_remove_pci_device = config.ghaf.hardware.definition.audio.removePciDevice != null; has_rescan_pci_device = config.ghaf.hardware.definition.audio.rescanPciDevice != null; has_acpi_path = config.ghaf.hardware.definition.audio.acpiPath != null; @@ -17,6 +25,7 @@ let config.ghaf.hardware.definition.audio.rescanPciDevice else config.ghaf.hardware.definition.audio.removePciDevice; + in { imports = [ @@ -24,27 +33,28 @@ in inputs.self.nixosModules.givc-host ]; options.ghaf.virtualization.microvm-host = { - enable = lib.mkEnableOption "MicroVM Host"; - networkSupport = lib.mkEnableOption "Network support services to run host applications."; + enable = mkEnableOption "MicroVM Host"; + networkSupport = mkEnableOption "Network support services to run host applications."; sharedVmDirectory = { - enable = lib.mkEnableOption "shared directory" // { + enable = mkEnableOption "shared directory" // { default = true; }; - vms = lib.mkOption { + vms = mkOption { description = '' List of names of virtual machines for which unsafe shared folder will be enabled. ''; - type = lib.types.listOf lib.types.str; + type = types.listOf types.str; default = [ ]; }; }; }; - config = lib.mkMerge [ - (lib.mkIf cfg.enable { + config = mkMerge [ + (mkIf cfg.enable { microvm.host.enable = true; microvm.host.useNotifySockets = true; + ghaf.systemd = { withName = "host-systemd"; enable = true; @@ -90,7 +100,7 @@ in }; }) - (lib.mkIf cfg.sharedVmDirectory.enable { + (mkIf cfg.sharedVmDirectory.enable { ghaf.virtualization.microvm.guivm.extraModules = [ (import ./common/shared-directory.nix "") ]; # Create directories required for sharing files with correct permissions. @@ -98,15 +108,20 @@ in let vmDirs = map ( n: - "d /storagevm/shared/shares/Unsafe\\x20${n}\\x20share/ 0700 ${config.ghaf.users.accounts.user} users" + "d /storagevm/shared/shares/Unsafe\\x20${n}\\x20share/ 0760 ${toString config.ghaf.users.loginUser.uid} users" ) cfg.sharedVmDirectory.vms; in [ "d /storagevm/shared 0755 root root" - "d /storagevm/shared/shares 0700 ${config.ghaf.users.accounts.user} users" + "d /storagevm/shared/shares 0760 ${toString config.ghaf.users.loginUser.uid} users" ] ++ vmDirs; - }) + { + # Add host directory for persistent home images + systemd.tmpfiles.rules = [ + "d /storagevm/homes 0770 microvm kvm -" + ]; + } ]; } diff --git a/modules/microvm/virtualization/microvm/netvm.nix b/modules/microvm/virtualization/microvm/netvm.nix index 9fa2830a5..e5c0b6500 100644 --- a/modules/microvm/virtualization/microvm/netvm.nix +++ b/modules/microvm/virtualization/microvm/netvm.nix @@ -4,20 +4,12 @@ { config, lib, - pkgs, ... }: let vmName = "net-vm"; macAddress = "02:00:00:01:01:01"; - isGuiVmEnabled = config.ghaf.virtualization.microvm.guivm.enable; - - sshKeysHelper = pkgs.callPackage ../../../../packages/ssh-keys-helper { - inherit pkgs; - inherit config; - }; - netvmBaseConfiguration = { imports = [ inputs.impermanence.nixosModules.impermanence @@ -43,7 +35,7 @@ let imports = [ ../../../common ]; ghaf = { - users.accounts.enable = lib.mkDefault config.ghaf.users.accounts.enable; + # Profiles profiles.debug.enable = lib.mkDefault config.ghaf.profiles.debug.enable; development = { # NOTE: SSH port also becomes accessible on the network interface @@ -52,6 +44,16 @@ let debug.tools.enable = lib.mkDefault config.ghaf.development.debug.tools.enable; nix-setup.enable = lib.mkDefault config.ghaf.development.nix-setup.enable; }; + users = { + proxyUser = { + enable = true; + extraGroups = [ + "networkmanager" + ]; + }; + }; + + # System systemd = { enable = true; withName = "netvm-systemd"; @@ -63,14 +65,19 @@ let withHardenedConfigs = true; }; givc.netvm.enable = true; - # Logging client configuration - logging.client.enable = config.ghaf.logging.client.enable; - logging.client.endpoint = config.ghaf.logging.client.endpoint; + + # Storage storagevm = { enable = true; - name = "netvm"; + name = vmName; directories = [ "/etc/NetworkManager/system-connections/" ]; }; + + # Services + # Logging client configuration + logging.client.enable = config.ghaf.logging.client.enable; + logging.client.endpoint = config.ghaf.logging.client.endpoint; + }; time.timeZone = config.time.timeZone; @@ -86,8 +93,6 @@ let firewall.allowedUDPPorts = [ 53 ]; }; - services.openssh = config.ghaf.security.sshKeys.sshAuthorizedKeysCommand; - # WORKAROUND: Create a rule to temporary hardcode device name for Wi-Fi adapter on x86 # TODO this is a dirty hack to guard against adding this to Nvidia/vm targets which # dont have that definition structure yet defined. FIXME. @@ -100,24 +105,14 @@ let # Optimize is disabled because when it is enabled, qemu is built without libusb optimize.enable = false; hypervisor = "qemu"; - shares = - [ - { - tag = "ro-store"; - source = "/nix/store"; - mountPoint = "/nix/.ro-store"; - proto = "virtiofs"; - } - ] - ++ lib.optionals isGuiVmEnabled [ - { - # Add the waypipe-ssh public key to the microvm - tag = config.ghaf.security.sshKeys.waypipeSshPublicKeyName; - source = config.ghaf.security.sshKeys.waypipeSshPublicKeyDir; - mountPoint = config.ghaf.security.sshKeys.waypipeSshPublicKeyDir; - proto = "virtiofs"; - } - ]; + shares = [ + { + tag = "ro-store"; + source = "/nix/store"; + mountPoint = "/nix/.ro-store"; + proto = "virtiofs"; + } + ]; writableStoreOverlay = lib.mkIf config.ghaf.development.debug.tools.enable "/nix/.rw-store"; qemu = { @@ -134,18 +129,6 @@ let ]; }; }; - - fileSystems = lib.mkIf isGuiVmEnabled { - ${config.ghaf.security.sshKeys.waypipeSshPublicKeyDir}.options = [ "ro" ]; - }; - - # SSH is very picky about to file permissions and ownership and will - # accept neither direct path inside /nix/store or symlink that points - # there. Therefore we copy the file to /etc/ssh/get-auth-keys (by - # setting mode), instead of symlinking it. - environment.etc = lib.mkIf isGuiVmEnabled { - ${config.ghaf.security.sshKeys.getAuthKeysFilePathInEtc} = sshKeysHelper.getAuthKeysSource; - }; } ) ]; diff --git a/modules/reference/personalize/keys.nix b/modules/reference/personalize/keys.nix index 6058c2263..b3e23e19d 100644 --- a/modules/reference/personalize/keys.nix +++ b/modules/reference/personalize/keys.nix @@ -31,7 +31,7 @@ in config = mkIf cfg.enable { users.users.root.openssh.authorizedKeys.keys = authorizedSshKeys; - users.users.${config.ghaf.users.accounts.user}.openssh.authorizedKeys.keys = authorizedSshKeys; + users.users.${config.ghaf.users.admin.name}.openssh.authorizedKeys.keys = authorizedSshKeys; ghaf.services.yubikey.u2fKeys = mkForce (concatStrings authorizedYubikeys); }; } diff --git a/packages/ghaf-powercontrol/default.nix b/packages/ghaf-powercontrol/default.nix index bd008830a..dfc4ba474 100644 --- a/packages/ghaf-powercontrol/default.nix +++ b/packages/ghaf-powercontrol/default.nix @@ -24,7 +24,7 @@ let useGivc = ghafConfig.givc.enable; # Handle Wayland display power state waylandDisplayCmd = command: '' - WAYLAND_DISPLAY=/run/user/${builtins.toString ghafConfig.users.accounts.uid}/wayland-0 \ + WAYLAND_DISPLAY=/run/user/${builtins.toString ghafConfig.users.loginUser.uid}/wayland-0 \ wlopm --${command} '*' ''; in diff --git a/packages/ghaf-xdg-open/default.nix b/packages/ghaf-xdg-open/default.nix index 47ad36850..d500094ca 100644 --- a/packages/ghaf-xdg-open/default.nix +++ b/packages/ghaf-xdg-open/default.nix @@ -6,6 +6,7 @@ dnsutils, openssh, sshKeyPath, + user, ... }: # This script is executed in the GUIVM by the Ghaf XDG systemd service when it receives an XDG open request. @@ -26,7 +27,6 @@ writeShellApplication { businessvmip=$(dig +short business-vm | head -1) commsvmip=$(dig +short comms-vm | head -1) - if [[ "127.0.0.1" != "$REMOTE_ADDR" && \ "$businessvmip" != "$REMOTE_ADDR" && \ "$googlechromevmip" != "$REMOTE_ADDR" && \ @@ -37,23 +37,23 @@ writeShellApplication { if [[ "127.0.0.1" != "$REMOTE_ADDR" ]]; then echo "Copying $sourcepath from $REMOTE_ADDR to $zathurapath in zathura-vm" - scp -i ${sshKeyPath} -o StrictHostKeyChecking=no "$REMOTE_ADDR":"$sourcepath" zathura-vm:"$zathurapath" + scp -i ${sshKeyPath} -o StrictHostKeyChecking=no ${user}@"$REMOTE_ADDR":"$sourcepath" ${user}@zathura-vm:"$zathurapath" else echo "Copying $sourcepath from GUIVM to $zathurapath in zathura-vm" - scp -i ${sshKeyPath} -o StrictHostKeyChecking=no "$sourcepath" zathura-vm:"$zathurapath" + scp -i ${sshKeyPath} -o StrictHostKeyChecking=no ${user}@"$sourcepath" ${user}@zathura-vm:"$zathurapath" fi echo "Opening $zathurapath in zathura-vm" if [[ "$type" == "pdf" ]]; then - ssh -i ${sshKeyPath} -o StrictHostKeyChecking=no zathura-vm run-waypipe zathura "'$zathurapath'" + ssh -i ${sshKeyPath} -o StrictHostKeyChecking=no ${user}@zathura-vm run-waypipe zathura "'$zathurapath'" elif [[ "$type" == "image" ]]; then - ssh -i ${sshKeyPath} -o StrictHostKeyChecking=no zathura-vm run-waypipe pqiv -i "'$zathurapath'" + ssh -i ${sshKeyPath} -o StrictHostKeyChecking=no ${user}@zathura-vm run-waypipe pqiv -i "'$zathurapath'" else echo "Unknown type: $type" fi echo "Deleting $zathurapath in zathura-vm" - ssh -i ${sshKeyPath} -o StrictHostKeyChecking=no zathura-vm rm -f "$zathurapath" + ssh -i ${sshKeyPath} -o StrictHostKeyChecking=no ${user}@zathura-vm rm -f "$zathurapath" ''; } diff --git a/packages/ssh-keys-helper/default.nix b/packages/ssh-keys-helper/default.nix index e0dabb719..9ebe05c77 100644 --- a/packages/ssh-keys-helper/default.nix +++ b/packages/ssh-keys-helper/default.nix @@ -6,7 +6,7 @@ source = let script = pkgs.writeShellScriptBin config.ghaf.security.sshKeys.getAuthKeysFileName '' - [[ "$1" != "ghaf" ]] && exit 0 + [[ "$1" != "${config.ghaf.users.appUser.name}" && "$1" != "${config.ghaf.users.admin.name}" ]] && exit 0 ${pkgs.coreutils}/bin/cat ${config.ghaf.security.sshKeys.waypipeSshPublicKeyFile} ''; in diff --git a/packages/wifi-signal-strength/default.nix b/packages/wifi-signal-strength/default.nix index 427e875a3..1fe1a6440 100644 --- a/packages/wifi-signal-strength/default.nix +++ b/packages/wifi-signal-strength/default.nix @@ -31,17 +31,17 @@ writeShellApplication { flock -w 60 -x 99 || exit 1 # Return the result as json format for waybar and use the control socket to close the ssh tunnel. - trap 'ssh -q -S /tmp/nmcli_socket -O exit ghaf@net-vm && cat "$NETWORK_STATUS_FILE"' EXIT + trap 'ssh -q -S /tmp/nmcli_socket -O exit ${config.ghaf.users.admin.name}@net-vm && cat "$NETWORK_STATUS_FILE"' EXIT # Connect to netvm ssh -M -S /tmp/nmcli_socket \ - -f -N -q ghaf@net-vm \ + -f -N -q ${config.ghaf.users.admin.name}@net-vm \ -i /run/waypipe-ssh/id_ed25519 \ -o StrictHostKeyChecking=no \ -o UserKnownHostsFile=/dev/null \ -o StreamLocalBindUnlink=yes \ -o ExitOnForwardFailure=yes \ - -L /tmp/ssh_session_dbus.sock:/run/user/${builtins.toString config.ghaf.users.accounts.uid}/bus \ + -L /tmp/ssh_session_dbus.sock:/run/user/${builtins.toString config.ghaf.users.admin.uid}/bus \ -L /tmp/ssh_system_dbus.sock:/run/dbus/system_bus_socket signal0="\UF091F" signal1="\UF0922"