Skip to content

Commit 9e9aa24

Browse files
Implement "insecure" mode.
The HOOKSHOT_INSECURE environment flag makes it so hookshot skips the signature verification phase. Also this includes a bunch of refactoring because I'm a bad programmer.
1 parent fbcdd43 commit 9e9aa24

File tree

1 file changed

+104
-87
lines changed

1 file changed

+104
-87
lines changed

src/cli.rs

Lines changed: 104 additions & 87 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ use router::Router;
1010
use server_config::{ServerConfig, Error, Environment};
1111
use signature::Signature;
1212
use std::env;
13+
use std::fmt::Display;
1314
use std::fs::File;
1415
use std::io::{Read, Write};
1516
use std::path::Path;
@@ -18,10 +19,27 @@ use task_manager::TaskManager;
1819
use uuid::Uuid;
1920

2021
const ENV_CONFIG_KEY: &'static str = "HOOKSHOT_CONFIG";
22+
const ENV_INSECURE_KEY: &'static str = "HOOKSHOT_INSECURE";
2123

2224
header! { (XHubSignature, "X-Hub-Signature") => [String] }
2325
header! { (XSignature, "X-Signature") => [String] }
2426

27+
struct TaskStatusPrinter {
28+
task_id: Uuid
29+
}
30+
impl TaskStatusPrinter {
31+
fn print<T: AsRef<str> + Display>(&self, msg: T) {
32+
println!("[{}]: {}", self.task_id, msg);
33+
}
34+
}
35+
36+
fn skip_signature_check() -> bool{
37+
match ENV_INSECURE_KEY {
38+
"true" | "t" | "1" => true,
39+
_ => false,
40+
}
41+
}
42+
2543
fn print_usage(program: &str, opts: Options) {
2644
let brief = format!("Usage: {} [options]", program);
2745
print!("{}", opts.usage(&brief));
@@ -38,15 +56,12 @@ pub fn main() {
3856
let matches = match opts.parse(&args[1..]) {
3957
Ok(m) => m,
4058
Err(f) => {
41-
println!("[error]: {}", f.to_string());
42-
print_usage(&program, opts);
43-
return;
59+
println!("[error]: {}", f);
60+
return print_usage(&program, opts);
4461
}
4562
};
4663
if matches.opt_present("h") {
47-
println!("printing out usage");
48-
print_usage(&program, opts);
49-
return;
64+
return print_usage(&program, opts);
5065
}
5166
let config_file = match matches.opt_str("c") {
5267
Some(file) => file,
@@ -55,10 +70,9 @@ pub fn main() {
5570
match env::var(ENV_CONFIG_KEY) {
5671
Ok(file) => file,
5772
Err(_) => {
58-
println!("[error]: Could not load config from environment or command \
59-
line.\n\nPass --config <FILE> option or set the HOOKSHOT_CONFIG \
60-
environment variable");
61-
return;
73+
return println!("[error]: Could not load config from environment or command \
74+
line.\n\nPass --config <FILE> option or set the HOOKSHOT_CONFIG \
75+
environment variable");
6276
}
6377
}
6478
}
@@ -68,18 +82,15 @@ pub fn main() {
6882
Ok(config) => start_server(config),
6983
Err(e) => match e {
7084
Error::FileOpenError | Error::FileReadError => {
71-
println!("[error]: Error opening or reading config file {}",
72-
config_file);
73-
return;
85+
return println!("[error]: Error opening or reading config file {}",
86+
config_file);
7487
}
7588
Error::ParseError => {
76-
println!("[error]: Could not parse {}, make sure it is valid TOML",
77-
config_file);
78-
return;
89+
return println!("[error]: Could not parse {}, make sure it is valid TOML",
90+
config_file);
7991
}
8092
_ => {
81-
println!("[error]: Could not validate file: {}", e);
82-
return;
93+
return println!("[error]: Could not validate file: {}", e);
8394
}
8495
},
8596
}
@@ -102,6 +113,8 @@ fn start_server(config: ServerConfig) {
102113
Ok(Response::with((Header(Connection::close()), status::Ok, "okay")))
103114
});
104115

116+
// Show the status of a specific task by UUID. If there is no log file by
117+
// that name or if the log file can't be read for any reason return a 404.
105118
let config_clone = config.clone();
106119
router.get("/tasks/:uuid", move |req: &mut Request| {
107120
let file_not_found = Ok(Response::with((Header(Connection::close()),
@@ -116,19 +129,14 @@ fn start_server(config: ServerConfig) {
116129
let logfile_path = Path::new(&config_clone.log_root.to_string())
117130
.join(format!("{}.log", uuid.to_string()));
118131

119-
let mut file = {
120-
match File::open(&logfile_path) {
121-
Ok(file) => file,
122-
Err(_) => return file_not_found,
123-
}
132+
let mut file = match File::open(&logfile_path) {
133+
Ok(file) => file,
134+
Err(_) => return file_not_found,
124135
};
125136

126-
let content = {
127-
let mut content = String::new();
128-
match file.read_to_string(&mut content) {
129-
Ok(_) => content,
130-
Err(_) => return file_not_found,
131-
}
137+
let mut content = String::new();
138+
if let Err(_) = file.read_to_string(&mut content) {
139+
return file_not_found;
132140
};
133141

134142
Ok(Response::with((Header(Connection::close()), status::Ok, content)))
@@ -139,75 +147,83 @@ fn start_server(config: ServerConfig) {
139147
let checkout_root = config.checkout_root.to_string();
140148
let config_clone = config.clone();
141149

142-
router.post("/hookshot", move |req: &mut Request| {
150+
router.post("/tasks", move |req: &mut Request| {
143151
let task_id = Uuid::new_v4();
152+
let task_status = TaskStatusPrinter { task_id: task_id };
144153
let log_root = &config_clone.log_root.to_string();
145-
println!("[{}]: request received, processing", task_id);
146-
147-
// Get the signature from the header. We support both `X-Hub-Signature` and
148-
// `X-Signature` but they both represent the same type underneath, a
149-
// string. It might eventually be better to put this functionality on the
150-
// Signature type itself.
151-
println!("[{}]: looking up signature", task_id);
152-
let signature = {
153-
let possible_headers = (req.headers.get::<XSignature>(),
154-
req.headers.get::<XHubSignature>());
155-
156-
let signature_string = match possible_headers {
157-
(Some(h), None) => h.to_string(),
158-
(None, Some(h)) => h.to_string(),
159-
(None, None) => {
160-
println!("[{}]: missing signature", task_id);
161-
return Ok(Response::with((Header(Connection::close()),
162-
status::Unauthorized,
163-
"missing signature")));
164-
}
165-
(Some(_), Some(_)) => {
166-
println!("[{}]: too many signatures", task_id);
167-
return Ok(Response::with((Header(Connection::close()),
168-
status::Unauthorized,
169-
"too many signatures")));
170-
}
171-
};
172154

173-
match Signature::from_str(&signature_string) {
174-
Some(signature) => signature,
175-
None => {
176-
println!("[{}]: could not parse signature", task_id);
177-
return Ok(Response::with((Header(Connection::close()),
178-
status::Unauthorized,
179-
"could not parse signature")));
155+
task_status.print("request received, processing");
156+
157+
let mut signature = None;
158+
if !skip_signature_check() {
159+
task_status.print("looking up signature");
160+
161+
// Get the signature from the header. We support both `X-Hub-Signature` and
162+
// `X-Signature` but they both represent the same type underneath, a
163+
// string. It might eventually be better to put this functionality on the
164+
// Signature type itself.
165+
signature = {
166+
let possible_headers = (req.headers.get::<XSignature>(),
167+
req.headers.get::<XHubSignature>());
168+
169+
let signature_string = match possible_headers {
170+
(Some(h), None) => h.to_string(),
171+
(None, Some(h)) => h.to_string(),
172+
(None, None) => {
173+
task_status.print("missing signature");
174+
return Ok(Response::with((Header(Connection::close()),
175+
status::Unauthorized,
176+
"missing signature")));
177+
}
178+
(Some(_), Some(_)) => {
179+
task_status.print("too many signatures");
180+
return Ok(Response::with((Header(Connection::close()),
181+
status::Unauthorized,
182+
"too many signatures")));
183+
}
184+
};
185+
186+
match Signature::from_str(&signature_string) {
187+
Some(signature) => Some(signature),
188+
None => {
189+
task_status.print("could not parse signature");
190+
return Ok(Response::with((Header(Connection::close()),
191+
status::Unauthorized,
192+
"could not parse signature")));
193+
}
180194
}
181-
}
182-
};
195+
};
196+
}
183197

184-
println!("[{}]: loading body into string", task_id);
198+
task_status.print("loading body into string");
185199
let mut payload = String::new();
186200
if req.body.read_to_string(&mut payload).is_err() {
187-
println!("[{}]: could not read body into string", task_id);
201+
task_status.print("could not read body into string");
188202
return Ok(Response::with((Header(Connection::close()), status::InternalServerError)));
189203
}
190204

191-
// Bail out if the signature doesn't match what we're expecting.
192-
println!("[{}]: signature found, verifying", task_id);
193-
if signature.verify(&payload, &config_clone.secret) == false {
194-
println!("[{}]: signature mismatch", task_id);
195-
return Ok(Response::with((Header(Connection::close()),
196-
status::Unauthorized,
197-
"signature doesn't match")));
205+
if !skip_signature_check() {
206+
// Bail out if the signature doesn't match what we're expecting.
207+
task_status.print("signature found, verifying");
208+
if signature.unwrap().verify(&payload, &config_clone.secret) == false {
209+
task_status.print("signature mismatch");
210+
return Ok(Response::with((Header(Connection::close()),
211+
status::Unauthorized,
212+
"signature doesn't match")));
213+
}
198214
}
199215

200216
// Try to parse the message.
201217
// TODO: we can be smarter about this. If we see the XHubSignature
202218
// above, we should try to parse as a github message, otherwise go
203219
// simple message.
204-
println!("[{}]: attempting to parse message from payload", task_id);
220+
task_status.print("attempting to parse message from payload");
205221
let repo = match SimpleMessage::from_str(&payload) {
206222
Ok(message) => GitRepo::from(message, &checkout_root),
207223
Err(_) => match GitHubMessage::from_str(&payload) {
208224
Ok(message) => GitRepo::from(message, &checkout_root),
209225
Err(_) => {
210-
println!("[{}]: could not parse message", task_id);
226+
task_status.print("could not parse message");
211227
return Ok(Response::with((Header(Connection::close()),
212228
status::BadRequest,
213229
"could not parse message")));
@@ -220,9 +236,8 @@ fn start_server(config: ServerConfig) {
220236
&repo.refstring) {
221237
Ok(environment) => environment,
222238
Err(_) => {
223-
println!("[{}]: warning: error loading environment for {}, definition flawed",
224-
task_id,
225-
repo.fully_qualified_branch());
239+
task_status.print(format!("warning: error loading environment for {}, definition flawed",
240+
repo.fully_qualified_branch()));
226241
Environment::new()
227242
}
228243
};
@@ -234,7 +249,7 @@ fn start_server(config: ServerConfig) {
234249
let mut logfile = match File::create(&logfile_path) {
235250
Ok(file) => file,
236251
Err(e) => {
237-
println!("[{}]: could not open logfile for writing: {}", task_id, e);
252+
task_status.print(format!("could not open logfile for writing: {}", e));
238253
return Ok(Response::with((Header(Connection::close()),
239254
status::InternalServerError)));
240255
}
@@ -249,26 +264,28 @@ fn start_server(config: ServerConfig) {
249264
secret: config_clone.secret.clone(),
250265
};
251266

252-
println!("[{}]: acquiring task manager lock", task_id);
267+
task_status.print("acquiring task manager lock");
253268
{
254269
let mut task_manager = shared_manager.lock().unwrap();
255270
let key = task_manager.ensure_queue(task.repo.fully_qualified_branch());
256271

257-
println!("[{}]: attempting to schedule", task_id);
272+
task_status.print("attempting to schedule");
258273
match task_manager.add_task(&key, task) {
259-
Ok(_) => println!("[{}]: scheduled", task_id),
274+
Ok(_) => task_status.print("scheduled"),
260275
Err(_) => {
261-
println!("[{}]: could not add task to queue", task_id);
276+
task_status.print("could not add task to queue");
262277
return Ok(Response::with((Header(Connection::close()),
263278
status::ServiceUnavailable)));
264279
}
265280
}
266281
}
267-
println!("[{}]: releasing task manager lock", task_id);
268-
println!("[{}]: request complete", task_id);
282+
task_status.print("releasing task manager lock");
283+
task_status.print("request complete");
269284

270285
logfile.write_all(b"task pending");
271286

287+
// TODO: probably shouldn't hardcode http://, someone might want to run
288+
// this behind HTTPS someday.
272289
let location = format!("http://{}:{}/tasks/{}",
273290
config_clone.hostname,
274291
config_clone.port,

0 commit comments

Comments
 (0)