|
| 1 | +use std::{io, path::{Path, PathBuf}, process::{ExitStatus, Command}}; |
| 2 | + |
| 3 | +use anyhow::Context; |
| 4 | +use serde::{Serialize, Deserialize}; |
| 5 | +use cargo_toml::Manifest; |
| 6 | +use thiserror::Error; |
| 7 | +use once_cell::sync::Lazy; |
| 8 | + |
| 9 | +/// Convenience macro to make command constructing containing |
| 10 | +/// optional args and flags readable. |
| 11 | +/// |
| 12 | +/// ```ignore |
| 13 | +/// command! { |
| 14 | +/// "command_name" => args( |
| 15 | +/// "--opt1" => ?is_some(value), // Where `value` is an Option<>. |
| 16 | +/// // `--opt1` is only passed if `value` |
| 17 | +/// // is Some() |
| 18 | +/// |
| 19 | +/// "--opt2" => ?is_true(flag), // Where `flag` is an bool. |
| 20 | +/// // `--opt2` is only passed if |
| 21 | +/// // `flag` is true. |
| 22 | +/// |
| 23 | +/// "--opt3" => val, // Where `val` is straight away |
| 24 | +/// // passed to command.arg() |
| 25 | +/// |
| 26 | +/// "--opt4", "--opt5" // args without values. |
| 27 | +/// ) |
| 28 | +/// } |
| 29 | +/// ``` |
| 30 | +macro_rules! command { |
| 31 | + { |
| 32 | + $name:expr $( => args( |
| 33 | + $( $arg:expr |
| 34 | + $( => ? is_true($flag:expr) )? |
| 35 | + $( => ? is_some($optional:expr) )? |
| 36 | + $( => $val:expr )? |
| 37 | + ),+ |
| 38 | + ) )? |
| 39 | + } => {{ |
| 40 | + let command = std::process::Command::new($name); |
| 41 | + |
| 42 | + $( |
| 43 | + let mut command = command; |
| 44 | + $( |
| 45 | + #[allow(unreachable_patterns)] |
| 46 | + match () { |
| 47 | + // case if arg is determined by a flag |
| 48 | + $( |
| 49 | + () if $flag => {command.arg($arg);} |
| 50 | + () => {}, |
| 51 | + )? |
| 52 | + |
| 53 | + // case if arg is determined by an optional val |
| 54 | + $( |
| 55 | + () if $optional.is_some() => {command.arg($arg).arg($optional.unwrap());} |
| 56 | + () => {}, |
| 57 | + )? |
| 58 | + |
| 59 | + // case if value can be given to arg straight away |
| 60 | + $( |
| 61 | + () => {command.arg($arg).arg($val);} |
| 62 | + )? |
| 63 | + |
| 64 | + // simple arg |
| 65 | + () => {command.arg($arg);}, |
| 66 | + }; |
| 67 | + )+ |
| 68 | + )? |
| 69 | + |
| 70 | + command |
| 71 | + }}; |
| 72 | +} |
| 73 | + |
| 74 | +static ARGS: Lazy<Vec<String>> = Lazy::new(|| { |
| 75 | + std::env::args().collect::<Vec<_>>() |
| 76 | +}); |
| 77 | + |
| 78 | +#[derive(Debug, Error)] |
| 79 | +enum CommandFail { |
| 80 | + #[error("Failed to run {0}: {1}")] |
| 81 | + Io(String, io::Error), |
| 82 | + #[error("While running {0} got exit status {1}")] |
| 83 | + Status(String, ExitStatus), |
| 84 | +} |
| 85 | + |
| 86 | +fn run_command(mut cmd: Command) -> Result<(), CommandFail> { |
| 87 | + match cmd.status() { |
| 88 | + Err(e) => Err(CommandFail::Io(format!("{:?}", cmd), e)), |
| 89 | + Ok(status) if status.success() => Ok(()), |
| 90 | + Ok(status) => Err(CommandFail::Status(format!("{:?}", cmd), status)), |
| 91 | + } |
| 92 | +} |
| 93 | + |
| 94 | +#[derive(Serialize, Deserialize, Debug)] |
| 95 | +#[serde(rename_all = "kebab-case")] |
| 96 | +struct CargoTomlMetadata { |
| 97 | + fortanix_vme: FortanixVmeConfig, |
| 98 | +} |
| 99 | + |
| 100 | +#[derive(Serialize, Deserialize, Debug)] |
| 101 | +#[serde(rename_all = "kebab-case", default, deny_unknown_fields)] |
| 102 | +#[rustfmt::skip] // contains long lines because of links and they may wrapped by mistake |
| 103 | +/// This config is mainly intended for args of ftxvme-elf2eif and nitro-cli run-enclave. |
| 104 | +/// See their args documentation for more info about these opts: |
| 105 | +/// https://docs.aws.amazon.com/enclaves/latest/user/cmd-nitro-run-enclave.html#cmd-nitro-run-enclave-options |
| 106 | +/// https://docs.aws.amazon.com/enclaves/latest/user/cmd-nitro-build-enclave.html#cmd-nitro-build-enclave-options |
| 107 | +struct FortanixVmeConfig { |
| 108 | + /// Enables verbose mode of ftxvme-elf2eif. |
| 109 | + verbose: bool, |
| 110 | + |
| 111 | + /// Path to output eif file |
| 112 | + eif_file_path: PathBuf, |
| 113 | + |
| 114 | + /// Path to resources, default is `/usr/share/nitro_enclaves/blobs/`. |
| 115 | + /// See blobs in: https://github.com/aws/aws-nitro-enclaves-cli#source-code-components |
| 116 | + resource_path: Option<PathBuf>, |
| 117 | + |
| 118 | + /// Path to signing certificate. If this is specified, |
| 119 | + /// `private-key` needs to be specified too. |
| 120 | + signing_certificate: Option<PathBuf>, |
| 121 | + |
| 122 | + /// Path to private key. If this is specified, |
| 123 | + /// `signing-certificate` needs to be specified too. |
| 124 | + private_key: Option<PathBuf>, |
| 125 | + |
| 126 | + /// A custom name given to the enclave. If not specified, |
| 127 | + /// the name of the .eif file is used. |
| 128 | + enclave_name: Option<String>, |
| 129 | + |
| 130 | + /// Specifies the number of vCPUs to allocate to the enclave. |
| 131 | + cpu_count: isize, |
| 132 | + |
| 133 | + /// Specifies the amount of memory (in MiB) to allocate to the enclave. |
| 134 | + /// Should be at least 64 MiB. |
| 135 | + memory: isize, |
| 136 | + |
| 137 | + /// `false` by default. This enables debug mode of `nitro-cli run-enclave`. |
| 138 | + debug_mode: bool, |
| 139 | +} |
| 140 | + |
| 141 | +impl FortanixVmeConfig { |
| 142 | + const DEFAULT_CPU_COUNT: isize = 2; |
| 143 | + const DEFAULT_MEMORY: isize = 512; |
| 144 | + |
| 145 | + fn default_eif_path() -> PathBuf { |
| 146 | + format!("{}.eif", ARGS[1]).into() |
| 147 | + } |
| 148 | + |
| 149 | + /// Tries to parse Cargo.toml for `package.metadata.fortanix-vme` and uses |
| 150 | + /// it if found. If some required values are missing in the the metadata, |
| 151 | + /// default ones are used. |
| 152 | + /// If no metadata is specified, we construct the config only using the |
| 153 | + /// default versions of required values. |
| 154 | + fn get() -> anyhow::Result<FortanixVmeConfig> { |
| 155 | + let manifest_path = Path::new(&std::env::var_os("CARGO_MANIFEST_DIR").context("CARGO_MANIFEST_DIR not set")?) |
| 156 | + .join("Cargo.toml"); |
| 157 | + |
| 158 | + let fortanix_vme_metadata = Manifest::<CargoTomlMetadata>::from_path_with_metadata(&manifest_path)? |
| 159 | + .package |
| 160 | + .map(|package| { |
| 161 | + package.metadata |
| 162 | + .map(|metadata| metadata.fortanix_vme) |
| 163 | + }) |
| 164 | + .flatten() |
| 165 | + .unwrap_or_default(); |
| 166 | + |
| 167 | + Ok(fortanix_vme_metadata) |
| 168 | + } |
| 169 | +} |
| 170 | + |
| 171 | +impl Default for FortanixVmeConfig { |
| 172 | + fn default() -> Self { |
| 173 | + Self { |
| 174 | + cpu_count: FortanixVmeConfig::DEFAULT_CPU_COUNT, |
| 175 | + memory: FortanixVmeConfig::DEFAULT_MEMORY, |
| 176 | + debug_mode: false, |
| 177 | + enclave_name: None, |
| 178 | + verbose: false, |
| 179 | + eif_file_path: FortanixVmeConfig::default_eif_path(), |
| 180 | + resource_path: None, |
| 181 | + signing_certificate: None, |
| 182 | + private_key: None, |
| 183 | + } |
| 184 | + } |
| 185 | +} |
| 186 | + |
| 187 | +fn main() -> anyhow::Result<()> { |
| 188 | + let fortanix_vme_config = FortanixVmeConfig::get()?; |
| 189 | + |
| 190 | + let ftxvme_elf2eif = command! { |
| 191 | + "ftxvme-elf2eif" => args( |
| 192 | + "--input-file" => &ARGS[1], |
| 193 | + "--output-file" => &fortanix_vme_config.eif_file_path, |
| 194 | + "--verbose" => ?is_true(fortanix_vme_config.verbose), |
| 195 | + "--resource-path" => ?is_some(fortanix_vme_config.resource_path) |
| 196 | + ) |
| 197 | + }; |
| 198 | + |
| 199 | + run_command(ftxvme_elf2eif)?; |
| 200 | + |
| 201 | + // We just try to start fortanix-vme-runner and don't wait on it. So, |
| 202 | + // we don't know if it errors out. |
| 203 | + // |
| 204 | + // fortanix-vme-runner starts a vsock proxy server and |
| 205 | + // is needed if your edp application makes any call to |
| 206 | + // functions like `TcpStream::connect()`. |
| 207 | + // If your application calls `TcpStream::connect("<url:port>")`, |
| 208 | + // this proxy server acts as a bridge for request and responses. |
| 209 | + let mut fortanix_vme_runner = command!("fortanix-vme-runner"); |
| 210 | + fortanix_vme_runner.spawn().context("Failed to start fortanix-vme-runner")?; |
| 211 | + |
| 212 | + let nitro_cli_run_enclave = command! { |
| 213 | + "nitro-cli" => args( |
| 214 | + "run-enclave", |
| 215 | + "--enclave-name" => ?is_some(fortanix_vme_config.enclave_name), |
| 216 | + "--cpu-count" => fortanix_vme_config.cpu_count.to_string(), |
| 217 | + "--eif-path" => &fortanix_vme_config.eif_file_path, |
| 218 | + "--memory" => fortanix_vme_config.memory.to_string(), |
| 219 | + "--debug-mode" => ?is_true(fortanix_vme_config.debug_mode) |
| 220 | + ) |
| 221 | + }; |
| 222 | + |
| 223 | + run_command(nitro_cli_run_enclave)?; |
| 224 | + |
| 225 | + Ok(()) |
| 226 | +} |
0 commit comments