Skip to main content

aegis_tool/app/
enroll_install.rs

1use aegis_dto::{HostId, protocol::AegisNetworkConfig};
2use anyhow::Result;
3use capulus::shell::shell_quote as sh_quote;
4
5use crate::cli::AgentMode;
6
7use super::{
8    REMOTE_HOST_CERT_PATH, REMOTE_HOST_KEY_PATH, install, mesh_bootstrap, system, wireguard,
9};
10
11pub(super) struct RemotePrepareHostScript;
12
13impl RemotePrepareHostScript {
14    pub(super) fn render(publish_ssh: bool) -> String {
15        format!(
16            "{}\n{}\n{}\nsudo \"$system_aegis\" agent prepare-identity {}\n",
17            system::prerequisites_script(true),
18            system_program_bootstrap_script(true),
19            crate::platform::BINARY_SHELL_ASSIGNMENT,
20            if publish_ssh { "--inbound-ssh" } else { "" }
21        )
22    }
23}
24
25pub(super) struct RemoteFinalizeInstall<'a> {
26    api_base: &'a str,
27    pending_host: &'a crate::config::CachedHost,
28    hub_peers: &'a [wireguard::HubPeer],
29    network: &'a AegisNetworkConfig,
30    server_certificate: Option<&'a str>,
31    agent_token: &'a str,
32    login_principal: &'a str,
33    initial_user_id: Option<&'a str>,
34    mode: AgentMode,
35    inbound_ssh: bool,
36}
37
38pub(super) struct RemoteFinalizeInstallParts<'a> {
39    pub(super) api_base: &'a str,
40    pub(super) pending_host: &'a crate::config::CachedHost,
41    pub(super) hub_peers: &'a [wireguard::HubPeer],
42    pub(super) network: &'a AegisNetworkConfig,
43    pub(super) server_certificate: Option<&'a str>,
44    pub(super) agent_token: &'a str,
45    pub(super) login_principal: &'a str,
46    pub(super) initial_user_id: Option<&'a str>,
47    pub(super) mode: AgentMode,
48    pub(super) inbound_ssh: bool,
49}
50
51impl<'a> RemoteFinalizeInstall<'a> {
52    pub(super) fn new(parts: RemoteFinalizeInstallParts<'a>) -> Self {
53        Self {
54            api_base: parts.api_base,
55            pending_host: parts.pending_host,
56            hub_peers: parts.hub_peers,
57            network: parts.network,
58            server_certificate: parts.server_certificate,
59            agent_token: parts.agent_token,
60            login_principal: parts.login_principal,
61            initial_user_id: parts.initial_user_id,
62            mode: parts.mode,
63            inbound_ssh: parts.inbound_ssh,
64        }
65    }
66
67    pub(super) fn render(&self) -> Result<String> {
68        let mut install_args = format!("--host-id {}", self.pending_host.host_id);
69        if self.server_certificate.is_some() {
70            install_args.push_str(&format!(
71                " --key {} --cert {}",
72                sh_quote(REMOTE_HOST_KEY_PATH),
73                sh_quote(REMOTE_HOST_CERT_PATH),
74            ));
75        }
76        install_args.push_str(&format!(
77            " --user {} --inbound-ssh {} --staged-enrollment",
78            sh_quote(self.login_principal),
79            if self.inbound_ssh { "yes" } else { "no" }
80        ));
81        if let Some(principal) = self.initial_user_id {
82            install_args.push_str(&format!(" --initial-user-id {}", sh_quote(principal)));
83        }
84        let agent_refresh_token_env =
85            install::agent_refresh_token_env_assignment(self.agent_token)?;
86        let wireguard_setup = if self.hub_peers.is_empty()
87            || self.pending_host.platform.operating_system
88                == aegis_dto::platform::OperatingSystem::MacOs
89        {
90            String::new()
91        } else {
92            mesh_bootstrap::BootstrapMeshScript::new(
93                self.pending_host,
94                self.hub_peers,
95                self.network,
96                self.mode,
97            )
98            .render()?
99        };
100        let host_certificate_bootstrap = self
101            .server_certificate
102            .map(|server_certificate| {
103                format!(
104                    "cat <<'EOF_AEGIS_SERVER_CERT' | sudo tee {REMOTE_HOST_CERT_PATH} >/dev/null\n\
105{server_certificate}\n\
106EOF_AEGIS_SERVER_CERT\n\
107sudo chmod 644 {REMOTE_HOST_CERT_PATH}\n"
108                )
109            })
110            .unwrap_or_default();
111        Ok(format!(
112            "set -euo pipefail\n\
113             sudo -v\n\
114             login_user={login_user}\n\
115             {wireguard_setup}\n\
116             {tool_bootstrap}\
117             {host_certificate_bootstrap}\
118             sudo env {agent_refresh_token_env} {system_binary} --api-base {api_base} advanced install {install_args}\n",
119            api_base = sh_quote(self.api_base),
120            agent_refresh_token_env = agent_refresh_token_env,
121            host_certificate_bootstrap = host_certificate_bootstrap,
122            install_args = install_args,
123            tool_bootstrap = system_program_bootstrap_script(true),
124            login_user = sh_quote(self.login_principal),
125            system_binary = crate::platform::system_binary_path(self.pending_host.platform),
126            wireguard_setup = wireguard_setup,
127        ))
128    }
129}
130
131pub fn system_program_bootstrap_script(use_sudo: bool) -> String {
132    format!(
133        "{sudo}bash -seuo pipefail <<'EOF_AEGIS_BOOTSTRAP'\naegis_bootstrap_version={version}\n{script}\nEOF_AEGIS_BOOTSTRAP\n",
134        sudo = if use_sudo { "sudo " } else { "" },
135        version = sh_quote(env!("CARGO_PKG_VERSION")),
136        script = include_str!("../../assets/bootstrap.sh"),
137    )
138}
139
140pub fn system_agent_activation_script() -> String {
141    format!(
142        "case $(uname -s) in\nDarwin)\n  if ! sudo launchctl print system/aegis-agent >/dev/null 2>&1; then\n    sudo launchctl bootstrap system /Library/LaunchDaemons/aegis-agent.plist\n  fi\n  sudo launchctl print system/aegis-agent >/dev/null\n  ;;\nLinux)\n  sudo systemctl enable --now {application_socket} {management_socket}\n  sudo systemctl enable --now {service}\n  sudo systemctl is-active --quiet {application_socket} {management_socket} {service}\n  ;;\n*) exit 1 ;;\nesac\n",
143        application_socket = sh_quote(crate::managed::APPLICATION_SOCKET_NAME),
144        management_socket = sh_quote(crate::managed::MANAGEMENT_SOCKET_NAME),
145        service = sh_quote(super::AEGIS_AGENT_SERVICE_NAME)
146    )
147}
148
149pub(super) struct LocalTargetInstall<'a> {
150    api_base: &'a str,
151    host_id: HostId,
152    inbound_ssh: bool,
153    install_host_certificate: bool,
154    agent_token: &'a str,
155    initial_user_id: Option<&'a str>,
156    system_bootstrap: String,
157}
158
159pub(super) struct LocalTargetInstallOptions<'a> {
160    pub api_base: &'a str,
161    pub host_id: HostId,
162    pub inbound_ssh: bool,
163    pub install_host_certificate: bool,
164    pub agent_token: &'a str,
165    pub initial_user_id: Option<&'a str>,
166}
167
168impl<'a> LocalTargetInstall<'a> {
169    pub(super) fn new(options: LocalTargetInstallOptions<'a>) -> Self {
170        Self {
171            api_base: options.api_base,
172            host_id: options.host_id,
173            inbound_ssh: options.inbound_ssh,
174            install_host_certificate: options.install_host_certificate,
175            agent_token: options.agent_token,
176            initial_user_id: options.initial_user_id,
177            system_bootstrap: system_program_bootstrap_script(false),
178        }
179    }
180
181    pub(super) fn run(&self) -> Result<()> {
182        system::LocalRoot::run_script(&self.system_bootstrap)?;
183        let mut install_args = vec![
184            "advanced".to_string(),
185            "install".to_string(),
186            "--staged-enrollment".to_string(),
187            "--host-id".to_string(),
188            self.host_id.to_string(),
189        ];
190        if self.install_host_certificate {
191            install_args.push("--key".to_string());
192            install_args.push(REMOTE_HOST_KEY_PATH.to_string());
193            install_args.push("--cert".to_string());
194            install_args.push(REMOTE_HOST_CERT_PATH.to_string());
195        }
196        install_args.push("--inbound-ssh".to_string());
197        install_args.push(if self.inbound_ssh {
198            "yes".to_string()
199        } else {
200            "no".to_string()
201        });
202        if let Some(principal) = self.initial_user_id {
203            install_args.push("--initial-user-id".to_string());
204            install_args.push(principal.to_string());
205        }
206        system::LocalRoot::run_aegis_command(self.api_base, &install_args, self.agent_token)
207    }
208}