Skip to content

Commit 73a31d9

Browse files
Merge #378
378: [PLAT-351]: Support an easy way to run Nitro enclaves r=MihirLuthra a=MihirLuthra Currently, to spawn an enclave, one has to do the following 3 steps: - Convert elf to eif - (not always required) Start fortanix-vme-runner which is basically a vsock proxy - nitro-cli run-enclave This PR adds code for a [custom runner for target `x86_64-unknown-linux-fortanixvme`](https://doc.rust-lang.org/cargo/reference/config.html#targettriplerunner) which will automate the above steps. This is pretty much like `ftxsgx-runner-cargo`. Although, I haven't created a separate `ftxvme-runner` like `ftxsgx-runner` as we can use `nitro-cli` directly here. This runner spawns `fortanix-vme-runner` (this can be disabled by options in metadata) but doesn't wait on it. So, it doesn't know its exit status. If `fortanix-vme-runner` has been spawned before, it will not cause issues for the runner as when it tries to spawn it again, the address will already be in use and nothing will happen (although, the error message is displayed). I think doing this should be enough as `fortanix-vme-runner` is being started here just for convenience. If someone wants to be certain of the exit status, they can set `disable-fortanix-vme-runner-start` to true in metadata and start `fortanix-vme-runner` explicitly. --- I have tested this by setting the following in `~/.cargo/config`: ```toml [target.x86_64-unknown-linux-fortanixvme] runner = "/path/to/bin/of/ftxvme-runner-cargo" ``` and then in a simple rust crate, ran: ``` CC=musl-gcc \ RUSTFLAGS="-Clink-self-contained=yes"\ cargo run --target ${VME_TARGET} -Zbuild-std ``` This runs the enclave and its output can be further monitored by `nitro-console`. Co-authored-by: Mihir Luthra <[email protected]>
2 parents 87f9893 + 49af63c commit 73a31d9

File tree

3 files changed

+269
-15
lines changed

3 files changed

+269
-15
lines changed

Cargo.lock

Lines changed: 39 additions & 15 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

fortanix-vme/eif-tools/Cargo.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,14 @@ categories = ["command-line-utilities"]
1515

1616
[dependencies]
1717
anyhow = "1.0"
18+
cargo_toml = "0.10.3"
1819
clap = "2.33"
1920
elf = "0.0.10"
2021
env_logger = "0.9"
2122
log = "0.4"
2223
nitro-cli = { git = "https://github.com/fortanix/aws-nitro-enclaves-cli.git", branch = "main" }
24+
once_cell = "1.9.0"
25+
serde = { version = "1.0", features = ["derive"] }
2326
sha2 = "0.9.5"
2427
tempdir = "0.3"
28+
thiserror = "1.0"
Lines changed: 226 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,226 @@
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

Comments
 (0)