diff --git a/CHANGELOG.md b/CHANGELOG.md --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,7 +15,6 @@ - `--path` now supports flakerefs (`github:foo/bar`, `git+file:///...`, `https://.../main.tar.gz`, etc). - `--flake` is now an alias for `--path`. -- wire will now attempt to use SSH `ControlMaster` by default. - A terminal bell will be output if a sudo / ssh prompt is ever printed. ### Fixed diff --git a/flake.nix b/flake.nix --- a/flake.nix +++ b/flake.nix @@ -42,11 +42,11 @@ ./doc ./tests/nix ./bench/run.nix + ./runtime ]; systems = import systems; flake = { - nixosModules.default = import ./runtime/module; makeHive = import ./runtime/makeHive.nix; hydraJobs = let diff --git a/runtime/default.nix b/runtime/default.nix new file mode 100644 --- /dev/null +++ b/runtime/default.nix @@ -0,0 +1,3 @@ +{ + flake.nixosModules.default = import ./module; +} diff --git a/doc/.vitepress/config.ts b/doc/.vitepress/config.ts --- a/doc/.vitepress/config.ts +++ b/doc/.vitepress/config.ts @@ -90,6 +90,7 @@ items: [ { text: "Install wire", link: "/guides/installation" }, { text: "Apply your Config", link: "/guides/apply" }, + { text: "Use a non-root user", link: "/guides/non-root-user" }, { text: "Target Nodes", link: "/guides/targeting" }, { text: "Flakes", diff --git a/doc/guides/non-root-user.md b/doc/guides/non-root-user.md new file mode 100644 --- /dev/null +++ b/doc/guides/non-root-user.md @@ -0,0 +1,54 @@ +--- +comment: true +title: Use a non-root user +description: Deploy as any user with wire. +--- + +# {{ $frontmatter.title }} + +{{ $frontmatter.description }} + +## Deploying User Requirements + +If your selected deployment user does not fit the following requirements, the +deployment commands will likely fail with an error: + +| | Password-based SSH | Non-interactive SSH Auth | +| :--------------------------------- | -----------------: | -----------------------: | +| In `wheel` (Sudo User) | ❌ Not Supported | ✅ Supported | +| Not In `wheel` (Unprivileged user) | ❌ Not Supported | ❌ Not Supported | + +- "In `wheel`" here meaning a sudoer, whether it be `root` or not. +- "Non-interactive SSH Auth" here most likely meaning an SSH key, anything that + does not require keyboard input in the terminal. + +## Changing the user + +By default, the target is set to root: + +```nix +{ + deployment.target.user = "root"; +} +``` + +But it can be any user you want so long as it fits the requirements above. + +```nix +{ + deployment.target.user = "root"; # [!code --] + deployment.target.user = "deploy-user"; # [!code ++] +} +``` + +After this change, wire will prompt you for sudo authentication, and tell you +the exact command wire wants privileged. + +```sh{6} +$ wire apply keys --on media + INFO eval_hive: evaluating hive Flake("/path/to/hive") +... + INFO media | step="Upload key @ NoFilter" progress="3/4" +me@node:22 | Authenticate for "sudo /nix/store/.../bin/key_agent": +[sudo] password for deploy-user: +``` diff --git a/runtime/module/options.nix b/runtime/module/options.nix --- a/runtime/module/options.nix +++ b/runtime/module/options.nix @@ -35,7 +35,8 @@ }; user = lib.mkOption { type = types.str; - description = "User to use for ssh."; + description = "User to use for SSH. The user must be atleast `wheel` and must use an SSH key or similar + non-interactive login method. More information can be found at https://wire.althaea.zone/guides/non-root-user"; default = "root"; }; port = lib.mkOption { diff --git a/wire/lib/src/hive/node.rs b/wire/lib/src/hive/node.rs --- a/wire/lib/src/hive/node.rs +++ b/wire/lib/src/hive/node.rs @@ -6,20 +6,17 @@ use gethostname::gethostname; use serde::{Deserialize, Serialize}; use std::assert_matches::debug_assert_matches; -use std::env; use std::fmt::Display; -use std::io::ErrorKind; -use std::path::PathBuf; use std::sync::Arc; use tokio::sync::oneshot; use tracing::{Instrument, Level, Span, debug, error, event, instrument, trace}; use crate::commands::common::evaluate_hive_attribute; use crate::commands::{CommandArguments, WireCommandChip, run_command}; -use crate::errors::{CommandError, NetworkError}; +use crate::errors::NetworkError; use crate::hive::HiveLocation; use crate::hive::steps::build::Build; -use crate::hive::steps::cleanup::{CleanUp, clean_up_control_master}; +use crate::hive::steps::cleanup::CleanUp; use crate::hive::steps::evaluate::Evaluate; use crate::hive::steps::keys::{Key, Keys, PushKeyAgent, UploadKeyAt}; use crate::hive::steps::ping::Ping; @@ -77,40 +74,13 @@ .to_string(), ]; - if modifiers.non_interactive || non_interactive_forced { - options.extend(["PasswordAuthentication=no".to_string()]); - options.extend(["KbdInteractiveAuthentication=no".to_string()]); - } - - let control_path = get_control_path().map_err(HiveLibError::CommandError)?; - options.extend([ - format!("ControlMaster={}", if master { "yes" } else { "no" }), - format!("ControlPath={control_path}"), - "ControlPersist=yes".to_string(), - ]); + options.extend(["PasswordAuthentication=no".to_string()]); + options.extend(["KbdInteractiveAuthentication=no".to_string()]); vector.push("-o".to_string()); vector.extend(options.into_iter().intersperse("-o".to_string())); Ok(vector) - } -} - -fn get_control_path() -> Result { - match env::var("XDG_RUNTIME_DIR") { - Ok(runtime_dir) => { - let control_path = PathBuf::from(runtime_dir).join("wire"); - - match std::fs::create_dir(&control_path) { - Err(err) if err.kind() != ErrorKind::AlreadyExists => { - return Err(CommandError::RuntimeDirectory(err)); - } - _ => (), - } - - Ok(control_path.join("%C").display().to_string()) - } - Err(err) => Err(CommandError::RuntimeDirectoryMissing(err)), } } @@ -227,14 +197,12 @@ } } - /// Tests the connection to a node, and sets up an SSH control master process in the background + /// Tests the connection to a node pub async fn ping(&self, modifiers: SubCommandModifiers) -> Result<(), HiveLibError> { - let _ = clean_up_control_master(self, modifiers).await; - let host = self.target.get_preferred_host()?; let command_string = format!( - "ssh {}@{host} {} -N", + "ssh {}@{host} {} exit", self.target.user, self.target.create_ssh_opts(modifiers, true)? ); @@ -718,11 +686,9 @@ "-o".to_string(), "StrictHostKeyChecking=accept-new".to_string(), "-o".to_string(), - "ControlMaster=no".to_string(), + "PasswordAuthentication=no".to_string(), "-o".to_string(), - format!("ControlPath={tmp}/wire/%C"), - "-o".to_string(), - "ControlPersist=yes".to_string(), + "KbdInteractiveAuthentication=no".to_string(), ]; assert_eq!( @@ -748,11 +714,9 @@ "-o".to_string(), "StrictHostKeyChecking=accept-new".to_string(), "-o".to_string(), - "ControlMaster=yes".to_string(), + "PasswordAuthentication=no".to_string(), "-o".to_string(), - format!("ControlPath={tmp}/wire/%C"), - "-o".to_string(), - "ControlPersist=yes".to_string(), + "KbdInteractiveAuthentication=no".to_string(), ] ); @@ -771,12 +735,6 @@ "PasswordAuthentication=no".to_string(), "-o".to_string(), "KbdInteractiveAuthentication=no".to_string(), - "-o".to_string(), - "ControlMaster=yes".to_string(), - "-o".to_string(), - format!("ControlPath={tmp}/wire/%C"), - "-o".to_string(), - "ControlPersist=yes".to_string(), ] );