1#![deny(missing_docs)]
4#![deny(warnings)]
5#![allow(clippy::too_many_arguments)]
6
7pub mod common;
11pub mod enclave_proc;
13pub mod enclave_proc_comm;
15pub 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
44pub const VMADDR_CID_HYPERVISOR: u32 = 0;
46
47pub const CID_TO_CONSOLE_PORT_OFFSET: u32 = 10000;
49
50const DEFAULT_BLOBS_PATH: &str = "/usr/share/nitro_enclaves/blobs/";
52
53pub 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
71pub 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
190pub 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 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 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
219pub 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 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
297pub 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
353fn blobs_path() -> NitroCliResult<String> {
363 let blobs_res = std::env::var("NITRO_CLI_BLOBS");
366
367 Ok(blobs_res.unwrap_or_else(|_| DEFAULT_BLOBS_PATH.to_string()))
368}
369
370fn 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
401pub 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
412pub 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
435pub 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 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 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
506pub 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
526pub 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 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
559pub fn get_file_pcr(path: String, pcr_type: PcrType) -> NitroCliResult<BTreeMap<String, String>> {
565 let mut key = "PCR".to_string();
566 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 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_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}