use std::sync::atomic::Ordering;
use super::*;
fn with_flag(region: &str) -> RunOptions {
let mut o = opts(region, "labs");
o.secrets.insert("FLAG", SecretValue::new("flag{it's $HOME}"));
o
}
#[tokio::test(start_paused = true)]
async fn a_flag_with_no_store_to_read_it_from_is_delivered_into_its_machine_over_ssh() {
let a = account_of(SOLO_WEB_AWS, 0);
let lab = a.runner.open(&a.folder.manifest, None).unwrap();
let events = Recorder::new();
lab.up(&with_flag("eu-west-3"), &events).await.unwrap();
let delivered = a.cloud.delivered.lock().unwrap().clone();
assert_eq!(delivered, [format!("FLAG {}\n", crate::encoding::base64(b"flag{it's $HOME}"))]);
let call = a.fake.last("/var/lib/isoloom/secrets").unwrap();
assert!(call.args.contains(&format!("admin@{PUBLIC}")), "the machine's address and login: {call:?}");
assert!(call.line().contains("sudo sh -c"), "{call:?}");
assert!(!call.line().contains("flag{"), "never on a command line");
assert!(!call.stdin.as_ref().unwrap().exists(), "the input goes once delivered");
let shown = serde_json::to_string(&events.events()).unwrap();
assert!(
!shown.contains("flag{") && !shown.contains(&crate::encoding::base64(b"flag{it's $HOME}")),
"never in an event"
);
let lines = a.fake.lines();
let deliver = lines.iter().position(|l| l.contains("/var/lib/isoloom/secrets")).unwrap();
assert!(deliver > lines.iter().position(|l| l.starts_with("terraform apply")).unwrap());
assert!(deliver < lines.iter().position(|l| l.contains("head -n 1")).unwrap());
}
#[tokio::test(start_paused = true)]
async fn a_machine_still_booting_gets_its_secrets_once_ssh_answers() {
let a = account_of(SOLO_WEB_AWS, 0);
a.cloud.unreachable.store(3, Ordering::SeqCst);
let lab = a.runner.open(&a.folder.manifest, None).unwrap();
lab.up(&with_flag("eu-west-3"), &Recorder::new()).await.unwrap();
assert_eq!(a.cloud.delivered.lock().unwrap().len(), 1);
assert_eq!(a.fake.calls().iter().filter(|c| c.line().contains("/var/lib/isoloom/secrets")).count(), 4);
}
#[tokio::test(start_paused = true)]
async fn a_machine_that_never_answers_fails_the_delivery_after_its_patience() {
let a = account_of(SOLO_WEB_AWS, 0);
a.cloud.unreachable.store(u32::MAX, Ordering::SeqCst);
let lab = a.runner.open(&a.folder.manifest, None).unwrap();
let err = lab.up(&with_flag("eu-west-3"), &Recorder::new()).await.unwrap_err();
assert_eq!(err.code(), crate::ErrorCode::ToolFailed);
assert!(err.to_string().contains("guest-web") && err.to_string().contains("Connection refused"), "{err}");
}
#[tokio::test(start_paused = true)]
async fn a_delivery_waiting_for_a_step_goes_right_after_it() {
let mut m: serde_json::Value = serde_json::from_str(SOLO_WEB_AWS).unwrap();
let flag = m["secrets"].as_array_mut().unwrap().iter_mut().find(|s| s["name"] == "FLAG").unwrap();
flag["deliver"][0]["during_step"] = serde_json::json!("apply");
let a = account_of(&m.to_string(), 0);
let lab = a.runner.open(&a.folder.manifest, None).unwrap();
lab.up(&with_flag("eu-west-3"), &Recorder::new()).await.unwrap();
assert_eq!(a.cloud.delivered.lock().unwrap().len(), 1, "once");
}
fn flag_in_a_state_file() -> String {
let mut m: serde_json::Value = serde_json::from_str(SOLO_WEB_AWS).unwrap();
let flag = m["secrets"].as_array_mut().unwrap().iter_mut().find(|s| s["name"] == "FLAG").unwrap();
flag["deliver"] = serde_json::json!([{"via": "state_file", "path": "inputs.tfvars.json", "format": "tfvars_json", "key": "flag"}]);
m["params"][0]["deliver"]
.as_array_mut()
.unwrap()
.push(serde_json::json!({"via": "state_file", "path": "inputs.tfvars.json", "format": "tfvars_json"}));
m.to_string()
}
#[tokio::test(start_paused = true)]
async fn values_in_a_state_file_are_written_at_up_kept_for_the_destroy_and_gone_after_it() {
let a = account_of(&flag_in_a_state_file(), 0);
let lab = a.runner.open(&a.folder.manifest, None).unwrap();
lab.up(&with_flag("eu-west-3"), &Recorder::new()).await.unwrap();
let file = a.runner.store.run_dir(lab.name()).unwrap().join("inputs.tfvars.json");
let written: serde_json::Value = serde_json::from_str(&std::fs::read_to_string(&file).unwrap()).unwrap();
assert_eq!(written, serde_json::json!({"flag": "flag{it's $HOME}", "region": "eu-west-3"}));
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
assert_eq!(std::fs::metadata(&file).unwrap().permissions().mode() & 0o777, 0o600);
}
let mut later = opts("us-east-1", "default");
later.secrets.insert("FLAG", SecretValue::new("another"));
lab.down(&later, &Recorder::new()).await.unwrap();
let seen: serde_json::Value = serde_json::from_str(a.cloud.destroyed_with.lock().unwrap().as_deref().unwrap()).unwrap();
assert_eq!(seen, written);
assert!(!file.exists(), "a successful down removes it");
}
fn with_a_state_secret() -> String {
let mut m: serde_json::Value = serde_json::from_str(&without_guest_delivery()).unwrap();
m["outputs"].as_array_mut().unwrap().push(serde_json::json!({
"name": "db_password", "role": "credential", "sensitive": false,
"from": {"kind": "terraform_output", "name": "db_password"}
}));
m["lab"]["message"] = serde_json::json!("The database takes ${outputs.db_password}; the site is at ${outputs.address_web}.");
m.to_string()
}
#[tokio::test(start_paused = true)]
async fn an_output_the_state_marks_sensitive_is_masked_and_never_in_the_message() {
let a = account_of(&with_a_state_secret(), 0);
let lab = a.runner.open(&a.folder.manifest, None).unwrap();
let events = Recorder::new();
let run = lab.up(&opts("eu-west-3", "labs"), &events).await.unwrap();
let secret = run.outputs.iter().find(|o| o.name == "db_password").unwrap();
assert!(secret.sensitive, "the state's word wins over the manifest's");
assert_eq!(
run.message.as_deref(),
Some("The database takes ${outputs.db_password}; the site is at 198.51.100.10.")
);
lab.check(&RunOptions::new(), &events).await.unwrap();
let shown = serde_json::to_string(&events.events()).unwrap();
assert!(!shown.contains("hunter2-db"), "never in an event: {shown}");
assert!(shown.contains("admin/***"), "a log line holding it is masked");
let status = lab.status().await.unwrap();
assert!(status.outputs.iter().any(|o| o.name == "db_password" && o.sensitive));
assert!(!status.message.unwrap_or_default().contains("hunter2-db"));
}
fn compose_over_ssh() -> String {
let mut m: serde_json::Value = serde_json::from_str(SOLO_WEB_AWS).unwrap();
m["lifecycle"]["up"].as_array_mut().unwrap().push(serde_json::json!({
"id": "compose-up", "action": "ssh.run",
"with": {"host": "${outputs.address_web}", "user": "${outputs.ssh_user}", "command": "cd /opt/isoloom && sudo -E docker compose up -d"}
}));
let flag = m["secrets"].as_array_mut().unwrap().iter_mut().find(|s| s["name"] == "FLAG").unwrap();
flag["deliver"] = serde_json::json!([{"via": "env", "var": "FLAG", "steps": ["compose-up"]}]);
m.to_string()
}
fn everything(a: &Account, lab: &crate::Lab<'_>, events: &Recorder) -> String {
let log = std::fs::read_to_string(a.runner.store.run_dir(lab.name()).unwrap().join("events.jsonl")).unwrap();
format!("{}\n{log}", serde_json::to_string(&events.events()).unwrap())
}
#[tokio::test(start_paused = true)]
async fn a_secret_carried_into_a_guest_step_never_shows_in_an_event_or_the_log() {
let a = account_of(&compose_over_ssh(), 0);
let lab = a.runner.open(&a.folder.manifest, None).unwrap();
let events = Recorder::new();
lab.up(&with_flag("eu-west-3"), &events).await.unwrap();
let call = a.fake.last("docker compose up").unwrap();
assert!(call.args.windows(2).any(|w| w == ["-o", "SendEnv=FLAG"]), "{call:?}");
assert_eq!(call.env("FLAG"), Some("flag{it's $HOME}"));
assert!(!call.line().contains("flag{"), "never on the command line");
let shown = everything(&a, &lab, &events);
assert!(shown.contains("starting with FLAG=***"), "its line, masked: {shown}");
assert!(!shown.contains("flag{"), "{shown}");
}
#[tokio::test(start_paused = true)]
async fn a_guest_step_that_fails_saying_a_secret_is_reported_masked() {
let a = account_of(&compose_over_ssh(), 0);
a.cloud.compose_fails.store(true, Ordering::SeqCst);
let lab = a.runner.open(&a.folder.manifest, None).unwrap();
let events = Recorder::new();
let err = lab.up(&with_flag("eu-west-3"), &events).await.unwrap_err();
assert!(err.to_string().contains("compose-up") && err.to_string().contains("FLAG=***"), "{err}");
let shown = everything(&a, &lab, &events);
assert!(!shown.contains("flag{"), "{shown}");
}