aegis-tool 0.4.5

Aegis SSH client and managed host agent.
Documentation
use std::process::Command;

use anyhow::{Result, anyhow, bail};
use capulus::managed::UnixAccount;

use crate::command::require_success;

pub(crate) struct ManagementOperatorSetup {
    principal: String,
    group_existed: bool,
}

impl ManagementOperatorSetup {
    pub(crate) fn prepare(principal: &str, managed_install_present: bool) -> Result<Self> {
        let account = UnixAccount::by_name(principal)?
            .ok_or_else(|| anyhow!("login principal `{principal}` has no NSS account"))?;
        account.validate_interactive()?;
        let group_existed = management_group_exists()?;
        if group_existed && !managed_install_present {
            bail!(
                "refusing to adopt pre-existing unmanaged Unix group `{}`",
                crate::managed::ACCESS_GROUP
            );
        }
        Ok(Self {
            principal: principal.to_string(),
            group_existed,
        })
    }

    pub(crate) fn apply(self) -> Result<()> {
        if self.group_existed != management_group_exists()? {
            bail!("Aegis management-group state changed while installation was prepared");
        }
        if cfg!(target_os = "macos") {
            if !self.group_existed {
                require_success(
                    "create the Aegis management group",
                    Command::new("/usr/sbin/dseditgroup").args([
                        "-o",
                        "create",
                        "-q",
                        crate::managed::ACCESS_GROUP,
                    ]),
                )?;
            }
            require_success(
                "authorize the Aegis management operator",
                Command::new("/usr/sbin/dseditgroup").args([
                    "-o",
                    "edit",
                    "-a",
                    &self.principal,
                    "-t",
                    "user",
                    crate::managed::ACCESS_GROUP,
                ]),
            )?;
        } else {
            if !self.group_existed {
                require_success(
                    "create the Aegis management group",
                    Command::new("/usr/sbin/groupadd")
                        .args(["--system", crate::managed::ACCESS_GROUP]),
                )?;
            }
            require_success(
                "authorize the Aegis management operator",
                Command::new("/usr/sbin/usermod").args([
                    "--append",
                    "--groups",
                    crate::managed::ACCESS_GROUP,
                    &self.principal,
                ]),
            )?;
        }
        Ok(())
    }
}

pub(crate) fn remove_management_group() -> Result<()> {
    if management_group_exists()? {
        let mut command = if cfg!(target_os = "macos") {
            let mut command = Command::new("/usr/sbin/dseditgroup");
            command.args(["-o", "delete"]);
            command
        } else {
            Command::new("/usr/sbin/groupdel")
        };
        require_success(
            "remove the Aegis management group",
            command.arg(crate::managed::ACCESS_GROUP),
        )?;
    }
    Ok(())
}

fn management_group_exists() -> Result<bool> {
    let output = if cfg!(target_os = "macos") {
        require_success(
            "inspect the Aegis management group",
            Command::new("/usr/bin/dscl").args([
                ".",
                "-read",
                &format!("/Groups/{}", crate::managed::ACCESS_GROUP),
            ]),
        )
    } else {
        require_success(
            "inspect the Aegis management group",
            Command::new("/usr/bin/getent").args(["group", crate::managed::ACCESS_GROUP]),
        )
    };
    Ok(output.is_ok())
}