liminal-cli 0.2.1

The liminal command: serve the network, maintain its stores, or serve its discovery MCP over stdio
Documentation
use std::cell::Cell;
use std::time::Duration;

use super::super::{Held, LysAgent, LysTable, PKCS8_ED25519_PREFIX, PassError};
use base64::Engine as _;
use std::io::Write as _;

type TestResult = Result<(), Box<dyn std::error::Error>>;

fn table() -> Result<LysTable, toml::de::Error> {
    toml::from_str("issuer = 'https://lys.example'")
}

fn source(agent: Option<&str>, pass: Option<&str>) -> Result<LysAgent, &'static str> {
    let table = table().map_err(|_| "table did not parse")?;
    LysAgent::new_with_env(&table, Duration::from_secs(1), |name| {
        Ok(match name {
            "LYS_AGENT" => agent.map(str::to_owned),
            "LYS_AGENT_PASS" => pass.map(str::to_owned),
            _ => None,
        })
    })
}

fn answer(audience: &str, expires_at: u64, pass: &str) -> Result<(u16, Vec<u8>), PassError> {
    serde_json::to_vec(&serde_json::json!({
        "pass": pass, "expires_at": expires_at, "audience": audience,
    }))
    .map(|body| (200, body))
    .map_err(|_| PassError("fixture answer did not encode".to_owned()))
}

#[test]
fn a_ticket_without_a_key_file_asks_with_the_run_pass_header() -> TestResult {
    let agent = source(Some("agent-1"), Some("fixture-run-pass"))?;
    let pass = agent.pass_for_at("liminal", 100, |route, request| {
        assert_eq!(route, "https://lys.example/api/agents/agent-1/pass");
        assert_eq!(request.header, "lys-agent-pass");
        assert_eq!(request.proof, "fixture-run-pass");
        let header = request.proof_header()?;
        assert!(header.is_sensitive());
        assert!(!format!("{header:?}").contains("fixture-run-pass"));
        assert_eq!(request.body, br#"{"audience":"liminal"}"#);
        answer("liminal", 120, "fixture-app-pass")
    })?;
    assert_eq!(pass, "fixture-app-pass");
    assert!(!format!("{agent:?}").contains("fixture-run-pass"));
    Ok(())
}

#[test]
fn a_missing_lys_agent_pass_is_refused_by_name() {
    for absent in [None, Some(""), Some("   ")] {
        assert_eq!(
            source(Some("agent-1"), absent).err(),
            Some("LYS_AGENT_PASS is missing or empty")
        );
    }
}

#[test]
fn a_missing_lys_agent_is_refused_by_name() {
    for absent in [None, Some(""), Some("   ")] {
        assert_eq!(
            source(absent, Some("fixture-run-pass")).err(),
            Some("LYS_AGENT is missing or empty")
        );
    }
}

#[test]
fn a_table_agent_that_differs_from_lys_agent_is_refused_by_name() -> TestResult {
    let table: LysTable = toml::from_str("issuer = 'https://lys.example'\nagent = 'agent-2'")?;
    let refused = LysAgent::new_with_env(&table, Duration::from_secs(1), |name| {
        Ok(Some(
            if name == "LYS_AGENT" {
                "agent-1"
            } else {
                "fixture-run-pass"
            }
            .to_owned(),
        ))
    });
    assert_eq!(refused.err(), Some("lys.agent differs from LYS_AGENT"));
    Ok(())
}

#[test]
fn a_run_pass_is_held_until_expiry_and_asked_again_per_audience() -> TestResult {
    let agent = source(Some("agent-1"), Some("fixture-run-pass"))?;
    let asks = Cell::new(0);
    let ask = |_: &str, request: &super::PassRequest| {
        asks.set(asks.get() + 1);
        let body: serde_json::Value = serde_json::from_slice(&request.body)
            .map_err(|_| PassError("fixture request did not read".to_owned()))?;
        let audience = body["audience"]
            .as_str()
            .ok_or_else(|| PassError("audience absent".to_owned()))?;
        answer(audience, 120 + asks.get(), &format!("pass-{}", asks.get()))
    };
    assert_eq!(agent.pass_for_at("liminal", 100, ask)?, "pass-1");
    assert_eq!(agent.pass_for_at("liminal", 120, ask)?, "pass-1");
    assert_eq!(asks.get(), 1);
    assert_eq!(agent.pass_for_at("haematite", 120, ask)?, "pass-2");
    assert_eq!(agent.pass_for_at("liminal", 121, ask)?, "pass-3");
    assert_eq!(asks.get(), 3);
    Ok(())
}

#[test]
fn a_run_pass_refusal_is_named_without_revealing_secrets() -> TestResult {
    let agent = source(Some("agent-1"), Some("fixture-run-pass"))?;
    let error = agent
        .pass_for_at("liminal", 100, |_, _| {
            Ok((
                403,
                br#"{"refusal":"agent_pass_unproven","detail":"fixture-run-pass"}"#.to_vec(),
            ))
        })
        .err()
        .ok_or("refusal was accepted")?;
    assert_eq!(
        error.0,
        "Lys refused the agent pass: agent_pass_unproven (HTTP 403)"
    );
    Ok(())
}

#[test]
fn a_run_pass_never_enters_a_transport_error() -> TestResult {
    let agent = source(Some("agent-1"), Some("fixture-run-pass"))?;
    let error = agent
        .pass_for_at("liminal", 100, |_, _| {
            Err(PassError("Lys transport unavailable".to_owned()))
        })
        .err()
        .ok_or("transport error was accepted")?;
    assert_eq!(error.0, "Lys transport unavailable");
    Ok(())
}

#[test]
fn a_table_with_a_key_file_signs_as_today() -> TestResult {
    let seed = [7_u8; 32];
    let der = [PKCS8_ED25519_PREFIX.as_slice(), seed.as_slice()].concat();
    let mut key = tempfile::NamedTempFile::new()?;
    write!(
        key,
        "-----BEGIN PRIVATE KEY-----\n{}\n-----END PRIVATE KEY-----\n",
        base64::engine::general_purpose::STANDARD.encode(der)
    )?;
    let table = LysTable {
        issuer: "https://lys.example".to_owned(),
        agent: "agent-1".to_owned(),
        key_file: key.path().to_owned(),
    };
    let agent = LysAgent::new_with_env(&table, Duration::from_secs(1), |_| {
        Err("the signed route must not read the run environment")
    })?;
    agent.pass_for_at("liminal", 100, |_, request| {
        assert_eq!(request.header, "lys-agent-signature");
        assert!(request.proof.starts_with("agent-1 100000 "));
        assert_eq!(request.body, br#"{"audience":"liminal"}"#);
        answer("liminal", 200, "fixture-app-pass")
    })?;
    Ok(())
}

#[test]
fn an_explicit_empty_key_or_agent_is_refused_in_the_table() {
    for field in ["key_file = ''", "agent = ''"] {
        assert!(
            toml::from_str::<LysTable>(&format!("issuer = 'https://lys.example'\n{field}"))
                .is_err()
        );
    }
}

#[test]
fn an_expired_admission_retires_only_the_liminal_pass() -> TestResult {
    let agent = source(Some("agent-1"), Some("fixture-run-pass"))?;
    for audience in ["liminal", "haematite"] {
        agent.pass_for_at(audience, 100, |_, _| answer(audience, 120, audience))?;
    }
    assert!(agent.forget_expired_pass()?);
    assert_eq!(
        agent.pass_for_at("haematite", 110, |_, _| {
            Err(PassError("another audience is still held".to_owned()))
        })?,
        "haematite"
    );
    assert_eq!(
        agent.pass_for_at("liminal", 110, |_, _| answer("liminal", 150, "fresh"))?,
        "fresh"
    );
    Ok(())
}

#[test]
fn an_invalid_run_agent_or_header_is_refused_without_its_value() {
    assert_eq!(
        source(Some("agent/secret"), Some("fixture-run-pass")).err(),
        Some("LYS_AGENT must be a plain agent id")
    );
    assert_eq!(
        source(Some("agent-1"), Some("secret\nvalue")).err(),
        Some("LYS_AGENT_PASS is not a valid header value")
    );
}

#[test]
fn transport_failure_names_the_cause_without_echoing_credential_text() {
    let error = ureq::Error::Other("fixture-run-pass".into());
    assert_eq!(super::transport_reason(&error), "other transport failure");
    let error = ureq::Error::Io(std::io::Error::new(
        std::io::ErrorKind::PermissionDenied,
        "fixture-run-pass",
    ));
    assert_eq!(super::transport_reason(&error), "I/O: permission denied");
}

#[test]
fn a_signed_source_is_unchanged_by_run_pass_admission_renewal() -> TestResult {
    let table = LysTable {
        issuer: "https://lys.example".to_owned(),
        agent: "agent-1".to_owned(),
        key_file: "/unused-key".into(),
    };
    let agent = LysAgent::new_with_env(&table, Duration::from_secs(1), |_| {
        Err("a signed source does not read run variables")
    })?;
    agent.lock()?.insert(
        "liminal".to_owned(),
        Held {
            pass: "held-signed-pass".to_owned(),
            expires_at: 200,
        },
    );
    assert!(!agent.forget_expired_pass()?);
    assert_eq!(
        agent.pass_for_at("liminal", 100, |_, _| {
            Err(PassError("the signed pass is still held".to_owned()))
        })?,
        "held-signed-pass"
    );
    Ok(())
}