diff --git a/src/integration/tests/boot.rs b/src/integration/tests/boot.rs index 5f6dd556..5b993832 100644 --- a/src/integration/tests/boot.rs +++ b/src/integration/tests/boot.rs @@ -189,22 +189,32 @@ async fn standard_boot_e2e() { assert_eq!( &stdout.next().unwrap().unwrap(), - "Is this the correct namespace name: quit-coding-to-vape? (yes/no)" + "Is this the correct namespace name: quit-coding-to-vape? (y/n)" ); - stdin.write_all("yes\n".as_bytes()).expect("Failed to write to stdin"); + stdin.write_all("y\n".as_bytes()).expect("Failed to write to stdin"); assert_eq!( &stdout.next().unwrap().unwrap(), - "Is this the correct namespace nonce: 2? (yes/no)" + "Is this the correct namespace nonce: 2? (y/n)" ); - stdin.write_all("yes\n".as_bytes()).expect("Failed to write to stdin"); + // On purpose, try to input a bad value, neither yes or no + stdin + .write_all("maybe\n".as_bytes()) + .expect("Failed to write to stdin"); assert_eq!( &stdout.next().unwrap().unwrap(), - "Is this the correct pivot restart policy: RestartPolicy::Never? (yes/no)" + "Please answer with either \"yes\" (y) or \"no\" (n)" ); + // Try the longer option ("yes" rather than "y") stdin.write_all("yes\n".as_bytes()).expect("Failed to write to stdin"); + assert_eq!( + &stdout.next().unwrap().unwrap(), + "Is this the correct pivot restart policy: RestartPolicy::Never? (y/n)" + ); + stdin.write_all("y\n".as_bytes()).expect("Failed to write to stdin"); + assert_eq!( &stdout.next().unwrap().unwrap(), "Are these the correct pivot args:" @@ -213,8 +223,8 @@ async fn standard_boot_e2e() { &stdout.next().unwrap().unwrap(), "[\"--msg\", \"testing420\"]?" ); - assert_eq!(&stdout.next().unwrap().unwrap(), "(yes/no)"); - stdin.write_all("yes\n".as_bytes()).expect("Failed to write to stdin"); + assert_eq!(&stdout.next().unwrap().unwrap(), "(y/n)"); + stdin.write_all("y\n".as_bytes()).expect("Failed to write to stdin"); // Wait for the command to write the approval and exit assert!(child.wait().unwrap().success()); @@ -390,19 +400,19 @@ async fn standard_boot_e2e() { // Answer prompts with yes assert_eq!( &stdout.next().unwrap().unwrap(), - "Is this the correct namespace name: quit-coding-to-vape? (yes/no)" + "Is this the correct namespace name: quit-coding-to-vape? (y/n)" ); stdin.write_all("yes\n".as_bytes()).expect("Failed to write to stdin"); assert_eq!( &stdout.next().unwrap().unwrap(), - "Is this the correct namespace nonce: 2? (yes/no)" + "Is this the correct namespace nonce: 2? (y/n)" ); stdin.write_all("yes\n".as_bytes()).expect("Failed to write to stdin"); assert_eq!( &stdout.next().unwrap().unwrap(), - "Does this AWS IAM role belong to the intended organization: arn:aws:iam::123456789012:role/Webserver? (yes/no)" + "Does this AWS IAM role belong to the intended organization: arn:aws:iam::123456789012:role/Webserver? (y/n)" ); stdin.write_all("yes\n".as_bytes()).expect("Failed to write to stdin"); diff --git a/src/integration/tests/preprod_sharding.rs b/src/integration/tests/preprod_sharding.rs index 86c8291b..a94ad422 100644 --- a/src/integration/tests/preprod_sharding.rs +++ b/src/integration/tests/preprod_sharding.rs @@ -88,9 +88,14 @@ fn preprod_reshard_ceremony() { .unwrap(); // For each of the enclaves... - for enclave_name in - ["ump", "evm-parser", "notarizer", "signer", "tls-fetcher", "deploy-test"] - { + for enclave_name in [ + "ump", + "evm-parser", + "notarizer", + "signer", + "tls-fetcher", + "deploy-test", + ] { // Decrypt the old dev share and assert that the resulting quorum key // has the right public key. Decrypted dev shares are _basically_ master // seeds. They're just have a "01" prefix because it's the one and only diff --git a/src/qos_client/src/cli/services.rs b/src/qos_client/src/cli/services.rs index 1deb8b74..7905b526 100644 --- a/src/qos_client/src/cli/services.rs +++ b/src/qos_client/src/cli/services.rs @@ -921,7 +921,7 @@ where // Check the namespace name { let prompt = format!( - "Is this the correct namespace name: {}? (yes/no)", + "Is this the correct namespace name: {}? (y/n)", manifest.namespace.name ); if !prompter.prompt_is_yes(&prompt) { @@ -932,7 +932,7 @@ where // Check the namespace nonce { let prompt = format!( - "Is this the correct namespace nonce: {}? (yes/no)", + "Is this the correct namespace nonce: {}? (y/n)", manifest.namespace.nonce ); if !prompter.prompt_is_yes(&prompt) { @@ -943,7 +943,7 @@ where // Check pivot restart policy { let prompt = format!( - "Is this the correct pivot restart policy: {:?}? (yes/no)", + "Is this the correct pivot restart policy: {:?}? (y/n)", manifest.pivot.restart ); if !prompter.prompt_is_yes(&prompt) { @@ -954,7 +954,7 @@ where // Check pivot arguments { let prompt = format!( - "Are these the correct pivot args:\n{:?}?\n(yes/no)", + "Are these the correct pivot args:\n{:?}?\n(y/n)", manifest.pivot.args ); if !prompter.prompt_is_yes(&prompt) { @@ -1352,7 +1352,7 @@ where // Check the namespace name { let prompt = format!( - "Is this the correct namespace name: {}? (yes/no)", + "Is this the correct namespace name: {}? (y/n)", manifest_envelope.manifest.namespace.name ); if !prompter.prompt_is_yes(&prompt) { @@ -1363,7 +1363,7 @@ where // Check the namespace nonce { let prompt = format!( - "Is this the correct namespace nonce: {}? (yes/no)", + "Is this the correct namespace nonce: {}? (y/n)", manifest_envelope.manifest.namespace.nonce ); if !prompter.prompt_is_yes(&prompt) { @@ -1374,7 +1374,7 @@ where // Check that the IAM role is correct { let prompt = format!( - "Does this AWS IAM role belong to the intended organization: {pcr3_preimage}? (yes/no)" + "Does this AWS IAM role belong to the intended organization: {pcr3_preimage}? (y/n)" ); if !prompter.prompt_is_yes(&prompt) { return false; @@ -1393,7 +1393,7 @@ where let approvers = approvers.join("\n"); let prompt = format!( - "The following manifest set members approved:\n{approvers}\nIs this ok? (yes/no)" + "The following manifest set members approved:\n{approvers}\nIs this ok? (y/n)" ); if !prompter.prompt_is_yes(&prompt) { @@ -1738,9 +1738,8 @@ pub(crate) fn shamir_reconstruct( }) .collect::>, Error>>()?; - let secret = Zeroizing::new( - qos_crypto::shamir::shares_reconstruct(shares).unwrap(), - ); + let secret = + Zeroizing::new(qos_crypto::shamir::shares_reconstruct(shares).unwrap()); write_with_msg(output_path.as_ref(), &secret, "Reconstructed secret"); @@ -2129,7 +2128,26 @@ where } fn prompt_is_yes(&mut self, question: &str) -> bool { - self.prompt(question) == "yes" + // Fixed amount of attempts to avoid a "while true" loop + let mut attempts = 3; + // First prompt is the question. Subsequent prompts (if any) are the reminder to answer either yes or no. + let mut prompt = question; + + while attempts > 0 { + let answer = self.prompt(prompt); + + if ["yes", "Yes", "YES", "Y", "y"].contains(&answer.as_ref()) { + return true; + } + + if ["no", "No", "NO", "N", "n"].contains(&answer.as_ref()) { + return false; + } + + attempts -= 1; + prompt = "Please answer with either \"yes\" (y) or \"no\" (n)"; + } + false } } @@ -2557,7 +2575,7 @@ mod tests { let Setup { manifest, .. } = setup(); let mut vec_out: Vec = vec![]; - let vec_in = "ye\n".as_bytes(); + let vec_in = "No\n".as_bytes(); let mut prompter = Prompter { reader: vec_in, writer: &mut vec_out }; @@ -2568,7 +2586,10 @@ mod tests { )); let output = String::from_utf8(vec_out).unwrap(); - assert_eq!(&output, "Is this the correct namespace name: test-namespace? (yes/no)\n"); + assert_eq!( + &output, + "Is this the correct namespace name: test-namespace? (y/n)\n" + ); } #[test] @@ -2591,7 +2612,7 @@ mod tests { assert_eq!( output[1], - "Is this the correct namespace nonce: 2? (yes/no)" + "Is this the correct namespace nonce: 2? (y/n)" ); } @@ -2615,7 +2636,7 @@ mod tests { assert_eq!( output[2], - "Is this the correct pivot restart policy: RestartPolicy::Never? (yes/no)" + "Is this the correct pivot restart policy: RestartPolicy::Never? (y/n)" ); } @@ -2639,7 +2660,7 @@ mod tests { assert_eq!(output[3], "Are these the correct pivot args:"); assert_eq!(output[4], "[\"--option1\", \"argument\"]?"); - assert_eq!(output[5], "(yes/no)"); + assert_eq!(output[5], "(y/n)"); } } @@ -2690,7 +2711,7 @@ mod tests { .get_mut(0) .unwrap() .member - .alias = "yoloswag420blazeit".to_string(); + .alias = "not-a-member".to_string(); let member = share_set.members[0].clone(); assert!(!proxy_re_encrypt_share_programmatic_verifications( @@ -2755,6 +2776,41 @@ mod tests { )); } + #[test] + fn accepts_with_some_typos_from_operator() { + let Setup { manifest_envelope, .. } = setup(); + + let mut vec_out: Vec = vec![]; + // Try all the accepted yes variants: y, yes, Yes, and YES! + let vec_in = "y\nyes\nyea\ntes\nYes\nYES\n".as_bytes(); + + let mut prompter = + Prompter { reader: vec_in, writer: &mut vec_out }; + + assert!(proxy_re_encrypt_share_human_verifications( + &manifest_envelope, + "pr3", + &mut prompter + )); + + let output = String::from_utf8(vec_out).unwrap(); + let output: Vec<_> = output.lines().collect(); + assert_eq!( + output, + vec![ + "Is this the correct namespace name: test-namespace? (y/n)", + "Is this the correct namespace nonce: 2? (y/n)", + "Does this AWS IAM role belong to the intended organization: pr3? (y/n)", + "Please answer with either \"yes\" (y) or \"no\" (n)", + "Please answer with either \"yes\" (y) or \"no\" (n)", + "The following manifest set members approved:", + "\talias: 0", + "\talias: 1", + "Is this ok? (y/n)", + ] + ); + } + #[test] fn exits_early_bad_namespace_name() { let Setup { manifest_envelope, .. } = setup(); @@ -2772,7 +2828,10 @@ mod tests { )); let output = String::from_utf8(vec_out).unwrap(); - assert_eq!(&output, "Is this the correct namespace name: test-namespace? (yes/no)\n"); + assert_eq!( + &output, + "Is this the correct namespace name: test-namespace? (y/n)\n" + ); } #[test] @@ -2795,7 +2854,7 @@ mod tests { let output: Vec<_> = output.lines().collect(); assert_eq!( output.last().unwrap(), - &"Is this the correct namespace nonce: 2? (yes/no)" + &"Is this the correct namespace nonce: 2? (y/n)" ); } @@ -2819,7 +2878,7 @@ mod tests { let output: Vec<_> = output.lines().collect(); assert_eq!( output.last().unwrap(), - &"Does this AWS IAM role belong to the intended organization: pr3? (yes/no)" + &"Does this AWS IAM role belong to the intended organization: pr3? (y/n)" ); } @@ -2828,7 +2887,7 @@ mod tests { let Setup { manifest_envelope, .. } = setup(); let mut vec_out: Vec = vec![]; - let vec_in = "yes\nyes\nyes\ny".as_bytes(); + let vec_in = "yes\nyes\nyes\nno".as_bytes(); let mut prompter = Prompter { reader: vec_in, writer: &mut vec_out }; @@ -2848,8 +2907,37 @@ mod tests { ); assert_eq!(output[4], "\talias: 0"); assert_eq!(output[5], "\talias: 1"); - assert_eq!(output[6], "Is this ok? (yes/no)"); + assert_eq!(output[6], "Is this ok? (y/n)"); assert_eq!(output.len(), 7); } + + #[test] + fn exits_after_three_hesitations() { + let Setup { manifest_envelope, .. } = setup(); + + let mut vec_out: Vec = vec![]; + let vec_in = "maybe\ndunno\nunsure\n".as_bytes(); + + let mut prompter = + Prompter { reader: vec_in, writer: &mut vec_out }; + + assert!(!proxy_re_encrypt_share_human_verifications( + &manifest_envelope, + "pr3", + &mut prompter + )); + + let output = String::from_utf8(vec_out).unwrap(); + let output: Vec<_> = output.lines().collect(); + + assert_eq!( + output, + vec![ + "Is this the correct namespace name: test-namespace? (y/n)", + "Please answer with either \"yes\" (y) or \"no\" (n)", + "Please answer with either \"yes\" (y) or \"no\" (n)", + ] + ); + } } }