//! The `gatewayd` program: its usage, what stops it at start, the warning for a secret in a file, //! and that the token never reaches its output (M4a spec, sections 3, 4 and 10). Do not edit. #[path = "support/tmp.rs"] mod tmp; use std::io::{BufRead, BufReader}; use std::os::unix::fs::PermissionsExt; use std::process::{Command, Stdio}; use std::time::{Duration, Instant}; use tmp::TempDir; const KYLE: &str = "k0000000000000000000000000"; const TOKEN: &str = "tok-3f9a1c7e5b2d4f6a8c0e"; fn gatewayd() -> Command { let mut c = Command::new(env!("CARGO_BIN_EXE_gatewayd")); c.env_remove("GW_TEST_TOKEN") .env_remove("CREDENTIALS_DIRECTORY"); c } fn closed_url() -> String { let port = std::net::TcpListener::bind("127.0.0.1:0") .unwrap() .local_addr() .unwrap() .port(); format!("http://127.0.0.1:{port}") } fn write_config(dir: &TempDir, secret: &str) -> std::path::PathBuf { let text = format!( "[mattermost]\nurl = \"{}\"\n[secrets.mattermost_token]\n{secret}\n[allow]\nusers = [\"{KYLE}\"]\n[paths]\nhome = \"{}\"\n", closed_url(), dir.path().join("home").display() ); dir.write("gatewayd.toml", &text) } /// Run until `want` appears on standard error or 5 s pass, then kill it; all it printed. fn stderr_until(mut cmd: Command, want: &str) -> String { let mut child = cmd .stderr(Stdio::piped()) .stdout(Stdio::null()) .spawn() .unwrap(); let mut reader = BufReader::new(child.stderr.take().unwrap()); let until = Instant::now() + Duration::from_secs(5); let mut all = String::new(); while Instant::now() < until && !all.contains(want) { let mut line = String::new(); if reader.read_line(&mut line).unwrap_or(0) == 0 { break; } all.push_str(&line); } let _ = child.kill(); let _ = child.wait(); all } #[test] fn usage() { for args in [ &[][..], &["serve"][..], &["serve", "--config"][..], &["run", "--config", "x"][..], ] { let out = gatewayd().args(args).output().unwrap(); assert_eq!(out.status.code(), Some(2), "{args:?}"); assert_eq!( String::from_utf8_lossy(&out.stderr), "usage: gatewayd serve --config \n" ); } } #[test] fn a_bad_config_stops_at_start() { let dir = TempDir::new("main-config"); let missing = dir.path().join("nope.toml"); let bad = dir.write("bad.toml", "[mattermost]\nurl = \"ftp://x\"\n"); for path in [missing, bad] { let out = gatewayd() .arg("serve") .arg("--config") .arg(&path) .output() .unwrap(); let err = String::from_utf8_lossy(&out.stderr); assert_eq!(out.status.code(), Some(1), "{err}"); assert!( err.starts_with(&format!("gatewayd: {}: ", path.display())), "{err}" ); assert!( err.ends_with("\nsee docs/runbook.md#gatewayd-start-failed\n"), "{err}" ); } } #[test] fn a_missing_secret_stops_at_start() { let dir = TempDir::new("main-secret"); let config = write_config(&dir, "env = \"GW_TEST_TOKEN\""); let out = gatewayd() .arg("serve") .arg("--config") .arg(&config) .output() .unwrap(); let err = String::from_utf8_lossy(&out.stderr); assert_eq!(out.status.code(), Some(1), "{err}"); assert!( err.starts_with("gatewayd: secret mattermost_token: "), "{err}" ); assert!( err.ends_with("\nsee docs/runbook.md#secret-unavailable\n"), "{err}" ); assert!( !dir.path().join("home").exists(), "nothing is made before the secret is read" ); } #[test] fn a_secret_in_a_file_warns_and_the_token_is_never_printed() { let dir = TempDir::new("main-file"); let secret = dir.write("token", &format!("{TOKEN}\n")); std::fs::set_permissions(&secret, std::fs::Permissions::from_mode(0o600)).unwrap(); let config = write_config(&dir, &format!("file = \"{}\"", secret.display())); let mut cmd = gatewayd(); cmd.arg("serve").arg("--config").arg(&config); let err = stderr_until(cmd, "trying again"); let warning = format!( "gatewayd: warning: secret mattermost_token is read in plaintext from {}; a systemd credential keeps it encrypted at rest (see docs/runbook.md#secret-in-a-file)\n", secret.display() ); assert!(err.starts_with(&warning), "{err}"); assert!( err.contains("gatewayd: cannot reach http://127.0.0.1:"), "{err}" ); assert!(!err.contains(TOKEN), "{err}"); let mode = std::fs::metadata(dir.path().join("home/gateway")) .unwrap() .permissions() .mode() & 0o777; assert_eq!(mode, 0o700); } #[test] fn a_secret_from_the_environment_has_no_warning() { let dir = TempDir::new("main-env"); let config = write_config(&dir, "env = \"GW_TEST_TOKEN\""); let mut cmd = gatewayd(); cmd.arg("serve") .arg("--config") .arg(&config) .env("GW_TEST_TOKEN", TOKEN); let err = stderr_until(cmd, "trying again"); assert!(err.starts_with("gatewayd: cannot reach "), "{err}"); assert!(!err.contains("warning") && !err.contains(TOKEN), "{err}"); }