Skip to content

Commit 411953f

Browse files
authored
Add support for unsigned arguments.
1 parent 7f8dd15 commit 411953f

File tree

2 files changed

+241
-2
lines changed

2 files changed

+241
-2
lines changed

crates/rrg/src/action/execute_signed_command.rs

Lines changed: 219 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,36 @@ impl std::fmt::Display for CommandExecutionError {
6363
}
6464
}
6565

66+
// An error indicating that the unsigned arg was required but not provided.
67+
#[derive(Debug)]
68+
struct MissingUnsignedArgError {
69+
idx: usize,
70+
}
71+
72+
impl std::fmt::Display for MissingUnsignedArgError {
73+
74+
fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
75+
write!(fmt, "missing unsigned arg at {}", self.idx)
76+
}
77+
}
78+
79+
impl std::error::Error for MissingUnsignedArgError {}
80+
81+
// An error indicating that there are more unsigned args provided than expected.
82+
#[derive(Debug)]
83+
struct ExcessiveUnsignedArgsError {
84+
count: usize,
85+
}
86+
87+
impl std::fmt::Display for ExcessiveUnsignedArgsError {
88+
89+
fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
90+
write!(fmt, "{} excessive unsigned arguments", self.count)
91+
}
92+
}
93+
94+
impl std::error::Error for ExcessiveUnsignedArgsError {}
95+
6696
impl std::error::Error for CommandExecutionError {}
6797

6898
/// Handles invocations of the `execute_signed_command` action.
@@ -327,6 +357,37 @@ impl crate::request::Args for Args {
327357
let path = std::path::PathBuf::try_from(command.take_path())
328358
.map_err(|error| ParseArgsError::invalid_field("command path", error))?;
329359

360+
let mut args = Vec::new();
361+
362+
// We use `args_signed` for compatibility reasons. Once the field is not
363+
// in active use anymore, this should be deleted.
364+
args.extend(command.take_args_signed());
365+
366+
let mut unsigned_args_iter = proto.take_unsigned_args().into_iter();
367+
368+
for (arg_idx, mut arg) in command.take_args().into_iter().enumerate() {
369+
let arg = if arg.unsigned_allowed() {
370+
match unsigned_args_iter.next() {
371+
Some(arg) => arg,
372+
None => {
373+
return Err(ParseArgsError::invalid_field("unsigned args", MissingUnsignedArgError {
374+
idx: arg_idx,
375+
}))
376+
}
377+
}
378+
} else {
379+
arg.take_signed()
380+
};
381+
382+
args.push(arg);
383+
}
384+
let unsigned_args_left = unsigned_args_iter.count();
385+
if unsigned_args_left > 0 {
386+
return Err(ParseArgsError::invalid_field("unsigned args", ExcessiveUnsignedArgsError {
387+
count: unsigned_args_left,
388+
}));
389+
}
390+
330391
let stdin = match command.unsigned_stdin_allowed() {
331392
true => proto.take_unsigned_stdin(),
332393
false => command.take_signed_stdin(),
@@ -338,7 +399,7 @@ impl crate::request::Args for Args {
338399
Ok(Args {
339400
raw_command,
340401
path,
341-
args: command.take_args(),
402+
args,
342403
env: command.take_env(),
343404
ed25519_signature,
344405
stdin,
@@ -925,6 +986,163 @@ mod tests {
925986
assert_eq!(item.stderr, b"");
926987
assert_eq!(item.stdout, b"");
927988
}
989+
990+
#[test]
991+
fn args_from_proto_args_signed() {
992+
let signing_key = ed25519_dalek::SigningKey::generate(&mut rand::rngs::OsRng);
993+
994+
let mut command_proto = rrg_proto::execute_signed_command::Command::new();
995+
command_proto.mut_path().set_raw_bytes(b"/foo/bar".into());
996+
command_proto.mut_args_signed().push(String::from("foo"));
997+
command_proto.mut_args_signed().push(String::from("bar"));
998+
999+
let mut arg_quux = rrg_proto::execute_signed_command::command::Arg::new();
1000+
arg_quux.set_signed(String::from("quux"));
1001+
command_proto.mut_args().push(arg_quux);
1002+
1003+
let mut arg_norf = rrg_proto::execute_signed_command::command::Arg::new();
1004+
arg_norf.set_signed(String::from("norf"));
1005+
command_proto.mut_args().push(arg_norf);
1006+
1007+
use protobuf::Message as _;
1008+
let command_bytes = command_proto.write_to_bytes()
1009+
.unwrap();
1010+
1011+
let mut args_proto = rrg_proto::execute_signed_command::Args::new();
1012+
args_proto.set_command_ed25519_signature(signing_key.sign(&command_bytes).to_vec());
1013+
args_proto.set_command(command_bytes);
1014+
1015+
let args = <Args as crate::request::Args>::from_proto(args_proto)
1016+
.unwrap();
1017+
assert_eq!(args.path, std::path::Path::new("/foo/bar"));
1018+
assert_eq!(args.args, ["foo", "bar", "quux", "norf"]);
1019+
}
1020+
1021+
#[test]
1022+
fn args_from_proto_args_unsigned() {
1023+
let signing_key = ed25519_dalek::SigningKey::generate(&mut rand::rngs::OsRng);
1024+
1025+
let mut command_proto = rrg_proto::execute_signed_command::Command::new();
1026+
command_proto.mut_path().set_raw_bytes(b"/foo/bar".into());
1027+
1028+
let mut arg_quux = rrg_proto::execute_signed_command::command::Arg::new();
1029+
arg_quux.set_unsigned_allowed(true);
1030+
command_proto.mut_args().push(arg_quux);
1031+
1032+
let mut arg_norf = rrg_proto::execute_signed_command::command::Arg::new();
1033+
arg_norf.set_unsigned_allowed(true);
1034+
command_proto.mut_args().push(arg_norf);
1035+
1036+
use protobuf::Message as _;
1037+
let command_bytes = command_proto.write_to_bytes()
1038+
.unwrap();
1039+
1040+
let mut args_proto = rrg_proto::execute_signed_command::Args::new();
1041+
args_proto.set_command_ed25519_signature(signing_key.sign(&command_bytes).to_vec());
1042+
args_proto.set_command(command_bytes);
1043+
args_proto.mut_unsigned_args().push(String::from("quux"));
1044+
args_proto.mut_unsigned_args().push(String::from("norf"));
1045+
1046+
let args = <Args as crate::request::Args>::from_proto(args_proto)
1047+
.unwrap();
1048+
assert_eq!(args.path, std::path::Path::new("/foo/bar"));
1049+
assert_eq!(args.args, ["quux", "norf"]);
1050+
}
1051+
1052+
#[test]
1053+
fn args_form_proto_args_mixed() {
1054+
let signing_key = ed25519_dalek::SigningKey::generate(&mut rand::rngs::OsRng);
1055+
1056+
let mut command_proto = rrg_proto::execute_signed_command::Command::new();
1057+
command_proto.mut_path().set_raw_bytes(b"/foo/bar".into());
1058+
1059+
let mut arg_quux = rrg_proto::execute_signed_command::command::Arg::new();
1060+
arg_quux.set_unsigned_allowed(true);
1061+
command_proto.mut_args().push(arg_quux);
1062+
1063+
let mut arg_norf = rrg_proto::execute_signed_command::command::Arg::new();
1064+
arg_norf.set_signed(String::from("norf"));
1065+
command_proto.mut_args().push(arg_norf);
1066+
1067+
let mut arg_thud = rrg_proto::execute_signed_command::command::Arg::new();
1068+
arg_thud.set_unsigned_allowed(true);
1069+
command_proto.mut_args().push(arg_thud);
1070+
1071+
use protobuf::Message as _;
1072+
let command_bytes = command_proto.write_to_bytes()
1073+
.unwrap();
1074+
1075+
let mut args_proto = rrg_proto::execute_signed_command::Args::new();
1076+
args_proto.set_command_ed25519_signature(signing_key.sign(&command_bytes).to_vec());
1077+
args_proto.set_command(command_bytes);
1078+
args_proto.mut_unsigned_args().push(String::from("quux"));
1079+
args_proto.mut_unsigned_args().push(String::from("thud"));
1080+
1081+
let args = <Args as crate::request::Args>::from_proto(args_proto)
1082+
.unwrap();
1083+
assert_eq!(args.path, std::path::Path::new("/foo/bar"));
1084+
assert_eq!(args.args, ["quux", "norf", "thud"]);
1085+
}
1086+
1087+
#[test]
1088+
fn args_from_proto_args_unsigned_missing() {
1089+
let signing_key = ed25519_dalek::SigningKey::generate(&mut rand::rngs::OsRng);
1090+
1091+
let mut command_proto = rrg_proto::execute_signed_command::Command::new();
1092+
command_proto.mut_path().set_raw_bytes(b"/foo/bar".into());
1093+
1094+
let mut arg_quux = rrg_proto::execute_signed_command::command::Arg::new();
1095+
arg_quux.set_unsigned_allowed(true);
1096+
command_proto.mut_args().push(arg_quux);
1097+
1098+
let mut arg_norf = rrg_proto::execute_signed_command::command::Arg::new();
1099+
arg_norf.set_unsigned_allowed(true);
1100+
command_proto.mut_args().push(arg_norf);
1101+
1102+
use protobuf::Message as _;
1103+
let command_bytes = command_proto.write_to_bytes()
1104+
.unwrap();
1105+
1106+
let mut args_proto = rrg_proto::execute_signed_command::Args::new();
1107+
args_proto.set_command_ed25519_signature(signing_key.sign(&command_bytes).to_vec());
1108+
args_proto.set_command(command_bytes);
1109+
args_proto.mut_unsigned_args().push(String::from("quux"));
1110+
1111+
// TODO(@panhania): Assert details of the error once exposed in
1112+
// `ParseArgsError`.
1113+
assert!(<Args as crate::request::Args>::from_proto(args_proto).is_err());
1114+
}
1115+
1116+
#[test]
1117+
fn args_from_proto_args_unsigned_excessive() {
1118+
let signing_key = ed25519_dalek::SigningKey::generate(&mut rand::rngs::OsRng);
1119+
1120+
let mut command_proto = rrg_proto::execute_signed_command::Command::new();
1121+
command_proto.mut_path().set_raw_bytes(b"/foo/bar".into());
1122+
1123+
let mut arg_quux = rrg_proto::execute_signed_command::command::Arg::new();
1124+
arg_quux.set_unsigned_allowed(true);
1125+
command_proto.mut_args().push(arg_quux);
1126+
1127+
let mut arg_norf = rrg_proto::execute_signed_command::command::Arg::new();
1128+
arg_norf.set_unsigned_allowed(true);
1129+
command_proto.mut_args().push(arg_norf);
1130+
1131+
use protobuf::Message as _;
1132+
let command_bytes = command_proto.write_to_bytes()
1133+
.unwrap();
1134+
1135+
let mut args_proto = rrg_proto::execute_signed_command::Args::new();
1136+
args_proto.set_command_ed25519_signature(signing_key.sign(&command_bytes).to_vec());
1137+
args_proto.set_command(command_bytes);
1138+
args_proto.mut_unsigned_args().push(String::from("quux"));
1139+
args_proto.mut_unsigned_args().push(String::from("norf"));
1140+
args_proto.mut_unsigned_args().push(String::from("thud"));
1141+
1142+
// TODO(@panhania): Assert details of the error once exposed in
1143+
// `ParseArgsError`.
1144+
assert!(<Args as crate::request::Args>::from_proto(args_proto).is_err());
1145+
}
9281146
}
9291147

9301148
#[cfg(test)]

proto/rrg/action/execute_signed_command.proto

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,26 @@ import "google/protobuf/duration.proto";
1111
import "rrg/fs.proto";
1212

1313
message Command {
14+
15+
message Arg {
16+
oneof arg {
17+
// Fixed argument to pass to the executed command.
18+
string signed = 1;
19+
20+
// Whether to allow execution of arbitrary argument passed in the request
21+
// without it being pre-signed.
22+
bool unsigned_allowed = 2;
23+
}
24+
}
25+
1426
// Path to the executable file to execute.
1527
rrg.fs.Path path = 1;
1628

1729
// Arguments to pass to the command.
18-
repeated string args = 2;
30+
repeated string args_signed = 2; // TODO: Deprecate this field.
31+
32+
// Arguments to pass to the command.
33+
repeated Arg args = 6;
1934

2035
// Environment in which to invoke the command.
2136
//
@@ -53,6 +68,12 @@ message Args {
5368
// arbitrary standard input by having the `unsigned_stdin_allowed` flag set.
5469
bytes unsigned_stdin = 2;
5570

71+
// Arguments to pass to the executed command.
72+
//
73+
// i-th argument specified here will be provided to the i-th argument of the
74+
// signed command that has `unsigned_arg_allowed` flag set.
75+
repeated string unsigned_args = 5;
76+
5677
// An [Ed25519][1] signature of the command.
5778
//
5879
// [1]: https://en.wikipedia.org/wiki/EdDSA#Ed25519

0 commit comments

Comments
 (0)