Skip to main content

nitro_cli/
lib.rs

1// Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
2// SPDX-License-Identifier: Apache-2.0
3#![deny(missing_docs)]
4#![deny(warnings)]
5#![allow(clippy::too_many_arguments)]
6
7//! This crate provides the functionality for the Nitro CLI process.
8
9/// The common module (shared between the CLI and enclave process).
10pub mod common;
11/// The enclave process module.
12pub mod enclave_proc;
13/// The module covering the communication between a CLI instance and enclave processes.
14pub mod enclave_proc_comm;
15/// The CLI-specific utilities module.
16pub mod utils;
17
18use aws_nitro_enclaves_image_format::defs::eif_hasher::EifHasher;
19use aws_nitro_enclaves_image_format::utils::eif_reader::EifReader;
20use aws_nitro_enclaves_image_format::utils::eif_signer::EifSigner;
21use aws_nitro_enclaves_image_format::utils::SignKeyData;
22use aws_nitro_enclaves_image_format::{generate_build_info, utils::get_pcrs};
23use log::{debug, info};
24use sha2::{Digest, Sha384};
25use std::collections::BTreeMap;
26use std::convert::TryFrom;
27use std::fs::{File, OpenOptions};
28use std::io::{self, Read, Write};
29use std::os::unix::net::UnixStream;
30use std::path::{Path, PathBuf};
31
32use common::commands_parser::{BuildEnclavesArgs, EmptyArgs, RunEnclavesArgs, SignEifArgs};
33use common::json_output::{
34    EifDescribeInfo, EnclaveBuildInfo, EnclaveTerminateInfo, MetadataDescribeInfo,
35};
36use common::{enclave_proc_command_send_single, get_sockets_dir_path};
37use common::{EnclaveProcessCommandType, NitroCliErrorEnum, NitroCliFailure, NitroCliResult};
38use enclave_proc_comm::{
39    enclave_proc_command_send_all, enclave_proc_handle_outputs, enclave_process_handle_all_replies,
40};
41
42use utils::{Console, PcrType};
43
44/// Hypervisor CID as defined by <http://man7.org/linux/man-pages/man7/vsock.7.html>.
45pub const VMADDR_CID_HYPERVISOR: u32 = 0;
46
47/// An offset applied to an enclave's CID in order to determine its console port.
48pub const CID_TO_CONSOLE_PORT_OFFSET: u32 = 10000;
49
50/// Default blobs path to be used if the corresponding environment variable is not set.
51const DEFAULT_BLOBS_PATH: &str = "/usr/share/nitro_enclaves/blobs/";
52
53/// Build an enclave image file with the provided arguments.
54pub fn build_enclaves(args: BuildEnclavesArgs) -> NitroCliResult<()> {
55    debug!("build_enclaves");
56    eprintln!("Start building the Enclave Image...");
57    build_from_docker(
58        &args.docker_uri,
59        &args.docker_dir,
60        &args.output,
61        &args.signing_certificate,
62        &args.private_key,
63        &args.img_name,
64        &args.img_version,
65        &args.metadata,
66    )
67    .map_err(|e| e.add_subaction("Failed to build EIF from docker".to_string()))?;
68    Ok(())
69}
70
71/// Build an enclave image file from a Docker image.
72pub fn build_from_docker(
73    docker_uri: &str,
74    docker_dir: &Option<String>,
75    output_path: &str,
76    signing_certificate: &Option<String>,
77    private_key: &Option<String>,
78    img_name: &Option<String>,
79    img_version: &Option<String>,
80    metadata_path: &Option<String>,
81) -> NitroCliResult<(File, BTreeMap<String, String>)> {
82    let blobs_path =
83        blobs_path().map_err(|e| e.add_subaction("Failed to retrieve blobs path".to_string()))?;
84    let cmdline_file_path = format!("{blobs_path}/cmdline");
85    let mut cmdline_file = File::open(cmdline_file_path.clone()).map_err(|e| {
86        new_nitro_cli_failure!(
87            &format!("Could not open kernel command line file: {e:?}"),
88            NitroCliErrorEnum::FileOperationFailure
89        )
90        .add_info(vec![&cmdline_file_path, "Open"])
91    })?;
92
93    let mut cmdline = String::new();
94    cmdline_file.read_to_string(&mut cmdline).map_err(|e| {
95        new_nitro_cli_failure!(
96            &format!("Failed to read kernel command line: {e:?}"),
97            NitroCliErrorEnum::FileOperationFailure
98        )
99        .add_info(vec![&cmdline_file_path, "Read"])
100    })?;
101
102    let mut file_output = OpenOptions::new()
103        .read(true)
104        .create(true)
105        .write(true)
106        .truncate(true)
107        .open(output_path)
108        .map_err(|e| {
109            new_nitro_cli_failure!(
110                &format!("Could not create output file: {e:?}"),
111                NitroCliErrorEnum::FileOperationFailure
112            )
113            .add_info(vec![output_path, "Open"])
114        })?;
115
116    let kernel_image_name = match std::env::consts::ARCH {
117        "aarch64" => "Image",
118        "x86_64" => "bzImage",
119        _ => "undefined",
120    };
121
122    let kernel_path = format!("{blobs_path}/{kernel_image_name}");
123    let build_info = generate_build_info!(&format!("{kernel_path}.config")).map_err(|e| {
124        new_nitro_cli_failure!(
125            &format!("Could not generate build info: {e:?}"),
126            NitroCliErrorEnum::EifBuildingError
127        )
128    })?;
129
130    let mut docker2eif = enclave_build::Docker2Eif::new(
131        docker_uri.to_string(),
132        format!("{blobs_path}/init"),
133        format!("{blobs_path}/nsm.ko"),
134        kernel_path,
135        cmdline.trim().to_string(),
136        format!("{blobs_path}/linuxkit"),
137        &mut file_output,
138        artifacts_path()?,
139        signing_certificate,
140        private_key,
141        img_name.clone(),
142        img_version.clone(),
143        metadata_path.clone(),
144        build_info,
145    )
146    .map_err(|err| {
147        new_nitro_cli_failure!(
148            &format!("Failed to create EIF image: {err:?}"),
149            NitroCliErrorEnum::EifBuildingError
150        )
151    })?;
152
153    if let Some(docker_dir) = docker_dir {
154        docker2eif
155            .build_docker_image(docker_dir.clone())
156            .map_err(|err| {
157                new_nitro_cli_failure!(
158                    &format!("Failed to build docker image: {err:?}"),
159                    NitroCliErrorEnum::DockerImageBuildError
160                )
161            })?;
162    } else {
163        docker2eif.pull_docker_image().map_err(|err| {
164            new_nitro_cli_failure!(
165                &format!("Failed to pull docker image: {err:?}"),
166                NitroCliErrorEnum::DockerImagePullError
167            )
168        })?;
169    }
170    let measurements = docker2eif.create().map_err(|err| {
171        new_nitro_cli_failure!(
172            &format!("Failed to create EIF image: {err:?}"),
173            NitroCliErrorEnum::EifBuildingError
174        )
175    })?;
176    eprintln!("Enclave Image successfully created.");
177
178    let info = EnclaveBuildInfo::new(measurements.clone());
179    println!(
180        "{}",
181        serde_json::to_string_pretty(&info).map_err(|err| new_nitro_cli_failure!(
182            &format!("Failed to display EnclaveBuild data: {err:?}"),
183            NitroCliErrorEnum::SerdeError
184        ))?
185    );
186
187    Ok((file_output, measurements))
188}
189
190/// Creates new enclave name
191///
192/// Requests the names of all running instances and checks the
193/// occurrence of the chosen name for the new enclave.
194pub fn new_enclave_name(run_args: RunEnclavesArgs, names: Vec<String>) -> NitroCliResult<String> {
195    let enclave_name = match run_args.enclave_name {
196        Some(enclave_name) => enclave_name,
197        None => {
198            // Get name of EIF file from path eg. path/to/eif/hello.eif -> hello
199            // If the extension is missing, the whole file name will be chosen
200            let path_split: Vec<&str> = run_args.eif_path.split('/').collect();
201            path_split[path_split.len() - 1]
202                .trim_end_matches(".eif")
203                .to_string()
204        }
205    };
206
207    let mut idx = 0;
208    let mut result_name = enclave_name.clone();
209
210    // If duplicates are found, add index to name eg. testName -> testName_1 -> testName_2 ..
211    while names.contains(&result_name) {
212        idx += 1;
213        result_name = enclave_name.clone() + &'_'.to_string() + &idx.to_string();
214    }
215
216    Ok(result_name)
217}
218
219/// Returns information related to the given EIF
220///
221/// Calculates PCRs 0, 1, 2, 8 at each call in addition to metadata,
222/// EIF details, identification provided by the user at build.
223pub fn describe_eif(eif_path: String) -> NitroCliResult<EifDescribeInfo> {
224    let mut eif_reader = EifReader::from_eif(eif_path).map_err(|e| {
225        new_nitro_cli_failure!(
226            &format!("Failed to initialize EIF reader: {e:?}"),
227            NitroCliErrorEnum::EifParsingError
228        )
229    })?;
230    let measurements = get_pcrs(
231        &mut eif_reader.image_hasher,
232        &mut eif_reader.bootstrap_hasher,
233        &mut eif_reader.app_hasher,
234        &mut eif_reader.cert_hasher,
235        Sha384::new(),
236        eif_reader.signature_section.is_some(),
237    )
238    .map_err(|e| {
239        new_nitro_cli_failure!(
240            &format!("Failed to get PCR values: {e:?}"),
241            NitroCliErrorEnum::EifParsingError
242        )
243    })?;
244
245    let mut describe_meta: Option<MetadataDescribeInfo> = None;
246    let mut img_name: Option<String> = None;
247    let mut img_version: Option<String> = None;
248
249    if let Some(meta) = eif_reader.get_metadata() {
250        img_name = Some(meta.img_name.clone());
251        img_version = Some(meta.img_version.clone());
252        describe_meta = Some(MetadataDescribeInfo::new(meta));
253    }
254
255    let mut info = EifDescribeInfo {
256        version: eif_reader.get_header().version,
257        build_info: EnclaveBuildInfo::new(measurements.clone()),
258        is_signed: false,
259        cert_info: None,
260        crc_check: eif_reader.check_crc(),
261        sign_check: None,
262        img_name,
263        img_version,
264        metadata: describe_meta,
265    };
266
267    // Check if signature section is present
268    if measurements.contains_key("PCR8") {
269        let cert_info = eif_reader
270            .get_certificate_info(measurements)
271            .map_err(|err| {
272                new_nitro_cli_failure!(
273                    &format!("Failed to get certificate sigining info: {err:?}"),
274                    NitroCliErrorEnum::EifParsingError
275                )
276            })?;
277        info.is_signed = true;
278        info.cert_info = Some(cert_info);
279        info.sign_check = eif_reader.sign_check;
280    }
281
282    println!(
283        "{}",
284        serde_json::to_string_pretty(&info)
285            .map_err(|err| {
286                new_nitro_cli_failure!(
287                    &format!("Failed to display EIF describe data: {err:?}"),
288                    NitroCliErrorEnum::SerdeError
289                )
290            })?
291            .as_str(),
292    );
293
294    Ok(info)
295}
296
297/// Signs EIF with the given key and certificate. If EIF already has a signature, it will be replaced.
298pub fn sign_eif(args: SignEifArgs) -> NitroCliResult<()> {
299    let sign_info = match (&args.private_key, &args.signing_certificate) {
300        (Some(key), Some(cert)) => SignKeyData::new(key, Path::new(&cert)).map_or_else(
301            |e| {
302                eprintln!("Could not read signing info: {e:?}");
303                None
304            },
305            Some,
306        ),
307        _ => None,
308    };
309
310    let signer = EifSigner::new(sign_info).ok_or_else(|| {
311        new_nitro_cli_failure!(
312            "Failed to create EifSigner".to_string(),
313            NitroCliErrorEnum::EIFSigningError
314        )
315    })?;
316
317    signer.sign_image(&args.eif_path).map_err(|e| {
318        new_nitro_cli_failure!(
319            format!("Failed to sign image: {}", e),
320            NitroCliErrorEnum::EIFSigningError
321        )
322    })?;
323
324    eprintln!("Enclave Image successfully signed.");
325
326    let mut eif_reader = EifReader::from_eif(args.eif_path).map_err(|e| {
327        new_nitro_cli_failure!(
328            &format!("Failed to initialize EIF reader: {e:?}"),
329            NitroCliErrorEnum::EifParsingError
330        )
331    })?;
332    eif_reader
333        .get_measurements()
334        .map_err(|e| {
335            new_nitro_cli_failure!(
336                &format!("Failed to get PCR values: {e:?}"),
337                NitroCliErrorEnum::EifParsingError
338            )
339        })
340        .and_then(|measurements| {
341            let info = EnclaveBuildInfo::new(measurements);
342            let printed_info = serde_json::to_string_pretty(&info).map_err(|err| {
343                new_nitro_cli_failure!(
344                    &format!("Failed to display EnclaveBuild data: {err:?}"),
345                    NitroCliErrorEnum::SerdeError
346                )
347            })?;
348            println!("{printed_info}");
349            Ok(())
350        })
351}
352
353/// Returns the value of the `NITRO_CLI_BLOBS` environment variable.
354///
355/// This variable specifies where all the blobs necessary for building
356/// an enclave image are stored. As of now the blobs are:
357/// - *bzImage*: A kernel image if the local arch is x86_64 or
358/// - *Image*  : A kernel image if the local arch is aarch64
359/// - *init*: The initial init process that is bootstraping the environment.
360/// - *linuxkit*: A slightly modified version of linuxkit.
361/// - *cmdline*: A file containing the kernel commandline.
362fn blobs_path() -> NitroCliResult<String> {
363    // TODO Improve error message with a suggestion to the user
364    // consider using the default path used by rpm install
365    let blobs_res = std::env::var("NITRO_CLI_BLOBS");
366
367    Ok(blobs_res.unwrap_or_else(|_| DEFAULT_BLOBS_PATH.to_string()))
368}
369
370/// Returns the value of the `NITRO_CLI_ARTIFACTS` environment variable.
371///
372/// This variable configures the path where the build artifacts should be saved.
373fn artifacts_path() -> NitroCliResult<String> {
374    if let Ok(artifacts) = std::env::var("NITRO_CLI_ARTIFACTS") {
375        std::fs::create_dir_all(artifacts.clone()).map_err(|e| {
376            new_nitro_cli_failure!(
377                &format!("Could not create artifacts path {artifacts}: {e:?}"),
378                NitroCliErrorEnum::FileOperationFailure
379            )
380            .add_info(vec![&artifacts, "Create"])
381        })?;
382        Ok(artifacts)
383    } else if let Ok(home) = std::env::var("HOME") {
384        let artifacts = format!("{home}/.nitro_cli/");
385        std::fs::create_dir_all(artifacts.clone()).map_err(|e| {
386            new_nitro_cli_failure!(
387                &format!("Could not create artifacts path {artifacts}: {e:?}"),
388                NitroCliErrorEnum::FileOperationFailure
389            )
390            .add_info(vec![&artifacts, "Create"])
391        })?;
392        Ok(artifacts)
393    } else {
394        Err(new_nitro_cli_failure!(
395            "Could not find a folder for the CLI artifacts, set either HOME or NITRO_CLI_ARTIFACTS",
396            NitroCliErrorEnum::ArtifactsPathNotSet
397        ))
398    }
399}
400
401/// Wrapper over the console connection function.
402pub fn console_enclaves(
403    enclave_cid: u64,
404    disconnect_timeout_sec: Option<u64>,
405) -> NitroCliResult<()> {
406    debug!("console_enclaves");
407    println!("Connecting to the console for enclave {enclave_cid}...");
408    enclave_console(enclave_cid, disconnect_timeout_sec)?;
409    Ok(())
410}
411
412/// Connects to the enclave console and prints it continously.
413pub fn enclave_console(
414    enclave_cid: u64,
415    disconnect_timeout_sec: Option<u64>,
416) -> NitroCliResult<()> {
417    let console = Console::new(
418        VMADDR_CID_HYPERVISOR,
419        u32::try_from(enclave_cid).map_err(|err| {
420            new_nitro_cli_failure!(
421                &format!("Failed to parse enclave CID: {err:?}"),
422                NitroCliErrorEnum::IntegerParsingError
423            )
424        })? + CID_TO_CONSOLE_PORT_OFFSET,
425    )
426    .map_err(|e| e.add_subaction("Connect to enclave console".to_string()))?;
427    println!("Successfully connected to the console.");
428    console
429        .read_to(io::stdout().by_ref(), disconnect_timeout_sec)
430        .map_err(|e| e.add_subaction("Connect to enclave console".to_string()))?;
431
432    Ok(())
433}
434
435/// Terminates all enclave instances belonging to the current user (or all
436/// instances, if the current user has `root` permissions).
437pub fn terminate_all_enclaves() -> NitroCliResult<()> {
438    let sockets_dir = get_sockets_dir_path();
439    let mut replies: Vec<UnixStream> = vec![];
440    let sockets = std::fs::read_dir(sockets_dir.as_path()).map_err(|e| {
441        new_nitro_cli_failure!(
442            &format!("Error while accessing sockets directory: {e:?}"),
443            NitroCliErrorEnum::FileOperationFailure
444        )
445        .add_info(vec![
446            sockets_dir
447                .as_path()
448                .to_str()
449                .unwrap_or("Invalid unicode directory name"),
450            "Read",
451        ])
452    })?;
453
454    let mut err_socket_files: usize = 0;
455    let mut failed_connections: Vec<PathBuf> = Vec::new();
456    for socket in sockets {
457        let entry = match socket {
458            Ok(value) => value,
459            Err(_) => {
460                err_socket_files += 1;
461                continue;
462            }
463        };
464
465        // Send a `terminate-enclave` command through each socket,
466        // irrespective of the enclave process owner. The security policy
467        // inside the enclave process is responsible with checking the
468        // command's permissions.
469        let mut stream = match UnixStream::connect(entry.path()) {
470            Ok(value) => value,
471            Err(_) => {
472                failed_connections.push(entry.path());
473                continue;
474            }
475        };
476
477        if enclave_proc_command_send_single::<EmptyArgs>(
478            EnclaveProcessCommandType::Terminate,
479            None,
480            &mut stream,
481        )
482        .is_err()
483        {
484            failed_connections.push(entry.path());
485        } else {
486            replies.push(stream);
487        }
488    }
489
490    // Remove stale socket files.
491    for stale_socket in &failed_connections {
492        info!("Deleting stale socket: {:?}", stale_socket);
493        let _ = std::fs::remove_file(stale_socket);
494    }
495
496    enclave_process_handle_all_replies::<EnclaveTerminateInfo>(
497        &mut replies,
498        failed_connections.len() + err_socket_files,
499        true,
500        vec![0, libc::EACCES],
501    )
502    .map_err(|e| e.add_subaction("Failed to handle all enclave processes replies".to_string()))
503    .map(|_| ())
504}
505
506/// Queries all enclaves for their name
507pub fn get_all_enclave_names() -> NitroCliResult<Vec<String>> {
508    let (comms, _) =
509        enclave_proc_command_send_all::<EmptyArgs>(EnclaveProcessCommandType::GetEnclaveName, None)
510            .map_err(|e| {
511                e.add_subaction(
512                    "Failed to send GetEnclaveName command to all enclave processes".to_string(),
513                )
514                .set_action("Get Enclave Names".to_string())
515            })?;
516
517    let mut replies: Vec<UnixStream> = vec![];
518    replies.extend(comms);
519    let objects = enclave_proc_handle_outputs::<String>(&mut replies)
520        .iter()
521        .map(|v| v.0.clone())
522        .collect();
523    Ok(objects)
524}
525
526/// Sends the name to all the running enclaves and expects a response
527/// with the ID of the one that uniquely matched
528pub fn get_id_by_name(name: String) -> NitroCliResult<String> {
529    let (comms, _) = enclave_proc_command_send_all::<String>(
530        EnclaveProcessCommandType::GetIDbyName,
531        Some(&name),
532    )
533    .map_err(|e| {
534        e.add_subaction("Failed to send GetIDbyName command to all enclave processes".to_string())
535            .set_action("Get Enclave Names".to_string())
536    })?;
537
538    let mut replies: Vec<UnixStream> = vec![];
539    replies.extend(comms);
540    let mut objects: Vec<String> = enclave_proc_handle_outputs::<String>(&mut replies)
541        .iter()
542        .map(|v| v.0.clone())
543        .collect();
544
545    // Check if the name was not found or if there are multiple matches
546    if objects.len() != 1 {
547        return Err(new_nitro_cli_failure!(
548            match objects.len() {
549                0 => "No enclave matched the given name.".to_string(),
550                _ => "Conflicting enclave names have been found.".to_string(),
551            },
552            NitroCliErrorEnum::EnclaveNamingError
553        ));
554    }
555
556    Ok(objects.remove(0))
557}
558
559/// For the given file, return the PCR value
560///
561/// Based on the pcr_type, calculate the PCR hash of the input. The default
562/// type takes the bytes of the input file and adds them to the hasher.
563/// The certificate type performs additional serialization before hashing.
564pub fn get_file_pcr(path: String, pcr_type: PcrType) -> NitroCliResult<BTreeMap<String, String>> {
565    let mut key = "PCR".to_string();
566    // Initialize hasher
567    let mut hasher = EifHasher::new_without_cache(Sha384::new()).map_err(|e| {
568        new_nitro_cli_failure!(
569            &format!("Could not create hasher: {e:?}"),
570            NitroCliErrorEnum::HasherError
571        )
572    })?;
573    let mut file = File::open(path).map_err(|e| {
574        new_nitro_cli_failure!(
575            &format!("Failed to open file: {e:?}"),
576            NitroCliErrorEnum::FileOperationFailure
577        )
578    })?;
579    let mut buf = Vec::new();
580    file.read_to_end(&mut buf).map_err(|e| {
581        new_nitro_cli_failure!(
582            &format!("Failed to read file: {e:?}"),
583            NitroCliErrorEnum::FileOperationFailure
584        )
585    })?;
586    // Treat the input buffer by PCR type
587    match pcr_type {
588        PcrType::DefaultType => {}
589        PcrType::SigningCertificate => {
590            key = "PCR8".to_string();
591            let cert = openssl::x509::X509::from_pem(&buf[..]).map_err(|e| {
592                new_nitro_cli_failure!(
593                    &format!("Failed to deserialize .pem: {e:?}"),
594                    NitroCliErrorEnum::HasherError
595                )
596            })?;
597            buf = cert.to_der().map_err(|e| {
598                new_nitro_cli_failure!(
599                    &format!("Failed to serialize certificate: {e:?}"),
600                    NitroCliErrorEnum::HasherError
601                )
602            })?;
603        }
604    }
605    hasher.write_all(&buf).map_err(|e| {
606        new_nitro_cli_failure!(
607            &format!("Could not write to hasher: {e:?}"),
608            NitroCliErrorEnum::HasherError
609        )
610    })?;
611    let hash = hex::encode(hasher.tpm_extend_finalize_reset().map_err(|e| {
612        new_nitro_cli_failure!(
613            &format!("Could not get result for hasher: {e:?}"),
614            NitroCliErrorEnum::HasherError
615        )
616    })?);
617
618    let mut result = BTreeMap::new();
619    result.insert(key, hash);
620    println!(
621        "{}",
622        serde_json::to_string_pretty(&result)
623            .map_err(|err| {
624                new_nitro_cli_failure!(
625                    &format!("Failed to display PCR(s): {err:?}"),
626                    NitroCliErrorEnum::SerdeError
627                )
628            })?
629            .as_str(),
630    );
631    Ok(result)
632}
633
634/// Macro defining the arguments configuration for a *Nitro CLI* application.
635#[macro_export]
636macro_rules! create_app {
637    () => {
638        Command::new("Nitro CLI")
639            .about("CLI for enclave lifetime management")
640            .arg_required_else_help(true)
641            .subcommand(
642                Command::new("run-enclave")
643                    .about("Starts a new enclave")
644                    .arg(
645                        Arg::new("cpu-ids")
646                            .long("cpu-ids")
647                            .help("List of cpu-ids that will be provided to the enclave")
648                            .num_args(1..)
649                            .required_unless_present_any(["cpu-count", "config"])
650                            .conflicts_with_all(["cpu-count", "config"]),
651                    )
652                    .arg(
653                        Arg::new("cpu-count")
654                            .long("cpu-count")
655                            .help("Number of cpus")
656                            .required_unless_present_any(["cpu-ids", "config"])
657                            .conflicts_with_all(["cpu-ids", "config"]),
658                    )
659                    .arg(
660                        Arg::new("memory")
661                            .long("memory")
662                            .help(
663                                "Memory to allocate for the enclave in MB. Depending on the available \
664                                pages, more might be allocated."
665                            )
666                            .required_unless_present("config")
667                            .conflicts_with("config"),
668                    )
669                    .arg(
670                        Arg::new("eif-path")
671                            .long("eif-path")
672                            .help("Path pointing to a prebuilt Eif image")
673                            .required_unless_present("config")
674                            .conflicts_with("config"),
675                    )
676                    .arg(
677                        Arg::new("enclave-cid")
678                            .long("enclave-cid")
679                            .help("CID to be used for the newly started enclave")
680                            .conflicts_with("config"),
681                    )
682                    .arg(
683                        Arg::new("debug-mode")
684                            .long("debug-mode")
685                            .action(clap::ArgAction::SetTrue)
686                            .help(
687                                "Starts enclave in debug-mode. This makes the console of the enclave \
688                                available over vsock at CID: VMADDR_CID_HYPERVISOR (0), port: \
689                                enclave_cid + 10000. \n The stream could be accessed with the console \
690                                sub-command"
691                            )
692                            .conflicts_with("config"),
693                    )
694                    .arg(
695                        Arg::new("attach-console")
696                            .long("attach-console")
697                            .action(clap::ArgAction::SetTrue)
698                            .help(
699                                "Attach the enclave console immediately after starting the enclave. \
700                                (implies debug-mode)"
701                            )
702                    )
703                    .arg(
704                        Arg::new("enclave-name")
705                            .long("enclave-name")
706                            .help("Custom name assigned to the enclave by the user")
707                            .conflicts_with("config"),
708                    )
709                    .arg(
710                        Arg::new("config")
711                            .long("config")
712                            .value_name("json-config")
713                            .help("Config is used to read enclave settings from JSON file"),
714                    ),
715            )
716            .subcommand(
717                Command::new("terminate-enclave")
718                    .about("Terminates an enclave")
719                    .arg(
720                        Arg::new("enclave-id")
721                            .long("enclave-id")
722                            .help("Enclave ID, used to uniquely identify an enclave")
723                            .required_unless_present_any(["all", "enclave-name"])
724                            .conflicts_with_all(["all", "enclave-name"]),
725                    )
726                    .arg(
727                        Arg::new("all")
728                            .long("all")
729                            .action(clap::ArgAction::SetTrue)
730                            .help("Terminate all running enclave instances belonging to the current user")
731                            .required_unless_present_any(["enclave-id", "enclave-name"])
732                            .conflicts_with_all(["enclave-id", "enclave-name"]),
733                    )
734                    .arg(
735                        Arg::new("enclave-name")
736                            .long("enclave-name")
737                            .help("Enclave name, used to uniquely identify an enclave")
738                            .required_unless_present_any(["enclave-id", "all"])
739                            .conflicts_with_all(["enclave-id", "all"]),
740                    ),
741            )
742            .subcommand(
743                Command::new("build-enclave")
744                    .about("Builds an enclave image and saves it to a file")
745                    .arg(
746                        Arg::new("docker-uri")
747                            .long("docker-uri")
748                            .help(
749                                "Uri pointing to an existing docker container or to be created \
750                                locally when docker-dir is present"
751                            )
752                            .required(true),
753                    )
754                    .arg(
755                        Arg::new("docker-dir")
756                            .long("docker-dir")
757                            .help("Local path to a directory containing a Dockerfile"),
758                    )
759                    .arg(
760                        Arg::new("output-file")
761                            .long("output-file")
762                            .help("Location where the Enclave Image should be saved")
763                            .required(true),
764                    )
765                    .arg(
766                        Arg::new("signing-certificate")
767                            .long("signing-certificate")
768                            .help("Local path to developer's X509 signing certificate.")
769                            .requires("private-key"),
770                    )
771                    .arg(
772                        Arg::new("private-key")
773                            .long("private-key")
774                            .help("KMS key ARN or local path to developer's Eliptic Curve private key.")
775                            .requires("signing-certificate"),
776                    )
777                    .arg(
778                        Arg::new("image_name")
779                            .long("name")
780                            .help("Name for enclave image"),
781                    )
782                    .arg(
783                        Arg::new("image_version")
784                            .long("version")
785                            .help("Version of the enclave image"),
786                    )
787                    .arg(
788                        Arg::new("metadata")
789                            .long("metadata")
790                            .help("Path to JSON containing the custom metadata provided by the user."),
791                    ),
792            )
793            .subcommand(
794                Command::new("describe-eif")
795                    .about("Returns information about the EIF found at a given path.")
796                    .arg(
797                        Arg::new("eif-path")
798                            .long("eif-path")
799                            .help("Path to the EIF to describe.")
800                            .required(true),
801                    ),
802            )
803            .subcommand(
804                Command::new("describe-enclaves")
805                    .about("Returns a list of the running enclaves")
806                    .arg(
807                        Arg::new("metadata")
808                            .long("metadata")
809                            .help("Adds EIF metadata of the current enclaves to the command output.")
810                            .action(clap::ArgAction::SetTrue)
811                        ),
812            )
813            .subcommand(
814                Command::new("console")
815                    .about("Connect to the console of an enclave")
816                    .arg(
817                        Arg::new("enclave-id")
818                            .long("enclave-id")
819                            .help("Enclave ID, used to uniquely identify an enclave")
820                            .required_unless_present("enclave-name")
821                            .conflicts_with("enclave-name"),
822                    )
823                    .arg(
824                        Arg::new("disconnect-timeout")
825                            .long("disconnect-timeout")
826                            .help("The time in seconds after the console disconnects from the enclave"),
827                    )
828                    .arg(
829                        Arg::new("enclave-name")
830                            .long("enclave-name")
831                            .help("Enclave name, used to uniquely identify an enclave")
832                            .required_unless_present("enclave-id")
833                            .conflicts_with("enclave-id"),
834                    ),
835            )
836            .subcommand(
837                Command::new("pcr")
838                    .about("Return the PCR hash value of the given input")
839                    .arg(
840                        Arg::new("signing-certificate")
841                            .long("signing-certificate")
842                            .help("Takes the path to the '.pem' signing certificate and returns PCR8. Can be used to identify the certificate used to sign an EIF")
843                            .required_unless_present("input")
844                            .conflicts_with("input"),
845                    )
846                    .arg(
847                        Arg::new("input")
848                            .long("input")
849                            .help("Given a path to a file, returns the PCR hash of the bytes it contains")
850                            .required_unless_present("signing-certificate")
851                            .conflicts_with("signing-certificate"),
852                    ),
853            )
854            .subcommand(
855                Command::new("explain")
856                    .about("Display detailed information about an error returned by a misbehaving Nitro CLI command")
857                    .arg(
858                        Arg::new("error-code")
859                            .long("error-code")
860                            .help("Error code, as returned by the misbehaving Nitro CLI command")
861                            .required(true),
862                    ),
863            )
864            .subcommand(
865                Command::new("sign-eif")
866                    .about("Sign EIF with the given key")
867                    .arg(
868                        Arg::new("eif-path")
869                            .long("eif-path")
870                            .help("Path pointing to a prebuilt Eif image")
871                    )
872                    .arg(
873                        Arg::new("signing-certificate")
874                            .long("signing-certificate")
875                            .help("Local path to developer's X509 signing certificate.")
876                            .requires("private-key"),
877                    )
878                    .arg(
879                        Arg::new("private-key")
880                            .long("private-key")
881                            .help("KMS key ARN or local path to developer's Eliptic Curve private key.")
882                            .requires("signing-certificate"),
883                    )
884            )
885    };
886}