|
73 | 73 | use std::io::{Read as _, Write as _}; |
74 | 74 | use crate::request::ParseArgsError; |
75 | 75 |
|
| 76 | + #[cfg(feature = "action-execute_signed_command-preverified")] |
| 77 | + { |
| 78 | + #[derive(Debug)] |
| 79 | + struct InvalidPreverifiedCommandsError(protobuf::Error); |
| 80 | + |
| 81 | + impl std::fmt::Display for InvalidPreverifiedCommandsError { |
| 82 | + |
| 83 | + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { |
| 84 | + write!(f, "invalid preverified commands: {}", self.0) |
| 85 | + } |
| 86 | + } |
| 87 | + |
| 88 | + impl std::error::Error for InvalidPreverifiedCommandsError { |
| 89 | + |
| 90 | + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { |
| 91 | + Some(&self.0) |
| 92 | + } |
| 93 | + } |
| 94 | + |
| 95 | + #[derive(Debug)] |
| 96 | + struct PreverifiedCommandError; |
| 97 | + |
| 98 | + impl std::fmt::Display for PreverifiedCommandError { |
| 99 | + |
| 100 | + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { |
| 101 | + write!(f, "no matching preverified command found") |
| 102 | + } |
| 103 | + } |
| 104 | + |
| 105 | + impl std::error::Error for PreverifiedCommandError { |
| 106 | + } |
| 107 | + |
| 108 | + use protobuf::Message as _; |
| 109 | + |
| 110 | + let commands = rrg_proto::execute_signed_command::CommandList::parse_from_bytes({ |
| 111 | + include_bytes!(env!("RRG_EXECUTE_SIGNED_COMMAND_PREVERIFIED")) |
| 112 | + }).map_err(|error| crate::session::Error::action(InvalidPreverifiedCommandsError(error)))?; |
| 113 | + |
| 114 | + if !commands.commands().iter().any(|command| &args.raw_command == command) { |
| 115 | + return Err(ParseArgsError::invalid_field("command", PreverifiedCommandError).into()); |
| 116 | + } |
| 117 | + } |
| 118 | + |
| 119 | + #[cfg(not(feature = "action-execute_signed_command-preverified"))] |
76 | 120 | match session.args().command_verification_key { |
77 | 121 | Some(key) => key |
78 | 122 | .verify_strict(&args.raw_command, &args.ed25519_signature) |
@@ -333,6 +377,12 @@ impl crate::response::Item for Item { |
333 | 377 | } |
334 | 378 |
|
335 | 379 | #[cfg(test)] |
| 380 | +// TODO: https://github.com/google/rrg/issues/137 |
| 381 | +// |
| 382 | +// Most of these tests rely on custom commands which does not work with the |
| 383 | +// predefined mode. Once support for predefined commands is gone, we can enable |
| 384 | +// them for all builds again. |
| 385 | +#[cfg(not(feature = "action-execute_signed_command-preverified"))] |
336 | 386 | mod tests { |
337 | 387 |
|
338 | 388 | use ed25519_dalek::Signer as _; |
@@ -876,3 +926,75 @@ mod tests { |
876 | 926 | assert_eq!(item.stdout, b""); |
877 | 927 | } |
878 | 928 | } |
| 929 | + |
| 930 | +#[cfg(test)] |
| 931 | +#[cfg(feature = "action-execute_signed_command-preverified")] |
| 932 | +mod tests { |
| 933 | + use super::*; |
| 934 | + |
| 935 | + // We just want to test preverification logic so we stick to Unix to keep |
| 936 | + // things simple and be able to rely on the `echo` command. |
| 937 | + #[cfg(target_family = "unix")] |
| 938 | + #[test] |
| 939 | + fn handle_all_preverified() { |
| 940 | + use protobuf::Message as _; |
| 941 | + |
| 942 | + let mut commands = rrg_proto::execute_signed_command::CommandList::parse_from_bytes({ |
| 943 | + include_bytes!(env!("RRG_EXECUTE_SIGNED_COMMAND_PREVERIFIED")) |
| 944 | + }).unwrap(); |
| 945 | + |
| 946 | + for raw_command in commands.take_commands() { |
| 947 | + let args = Args { |
| 948 | + raw_command, |
| 949 | + // In this test we use real raw preverified commands but only to |
| 950 | + // ensure that the verification lets it through. For actual exe- |
| 951 | + // ecution we just run `echo` (as this is safe and preverified |
| 952 | + // commands could have some dangerous stuff in there). |
| 953 | + path: "echo".into(), |
| 954 | + args: vec![String::from("foo")], |
| 955 | + env: std::collections::HashMap::new(), |
| 956 | + // Again, we provide a signature of just 0. This should not pass |
| 957 | + // the normal verification but we want to test that it is not |
| 958 | + // actually verified. |
| 959 | + ed25519_signature: ed25519_dalek::Signature::from_bytes({ |
| 960 | + &[0; ed25519_dalek::Signature::BYTE_SIZE] |
| 961 | + }), |
| 962 | + stdin: Vec::from(b""), |
| 963 | + timeout: std::time::Duration::from_secs(5), |
| 964 | + }; |
| 965 | + |
| 966 | + let mut session = crate::session::FakeSession::new(); |
| 967 | + handle(&mut session, args).unwrap(); |
| 968 | + |
| 969 | + assert_eq!(session.reply_count(), 1); |
| 970 | + |
| 971 | + let item = session.reply::<Item>(0); |
| 972 | + assert!(item.exit_status.success()); |
| 973 | + assert_eq!(item.stdout, b"foo\n"); |
| 974 | + assert_eq!(item.stderr, b""); |
| 975 | + } |
| 976 | + } |
| 977 | + |
| 978 | + #[test] |
| 979 | + fn handle_unverified() { |
| 980 | + use protobuf::Message as _; |
| 981 | + |
| 982 | + let mut command = rrg_proto::execute_signed_command::Command::new(); |
| 983 | + command.mut_path().set_raw_bytes(b"/usr/sbin/iamnotverified".to_vec()); |
| 984 | + |
| 985 | + let args = Args { |
| 986 | + raw_command: command.write_to_bytes().unwrap(), |
| 987 | + path: "/usr/sbin/iamnotverified".into(), |
| 988 | + args: vec![], |
| 989 | + env: std::collections::HashMap::new(), |
| 990 | + ed25519_signature: ed25519_dalek::Signature::from_bytes({ |
| 991 | + &[0; ed25519_dalek::Signature::BYTE_SIZE] |
| 992 | + }), |
| 993 | + stdin: Vec::from(b""), |
| 994 | + timeout: std::time::Duration::from_secs(5), |
| 995 | + }; |
| 996 | + |
| 997 | + let mut session = crate::session::FakeSession::new(); |
| 998 | + assert!(handle(&mut session, args).is_err()); |
| 999 | + } |
| 1000 | +} |
0 commit comments