fabro/lib/components/fabro-petri/tests/secrets.rs
Scott Werner 6ac6d5495f fix: run built-in sandbox providers in process
Register lazy Host, Docker, and Daytona factories for Petri execution,
fork, and prune. Share server provider configuration, preserve lease
fingerprints, and source Daytona credentials from the vault.

Remove built-in plugin setup and skip gates; add a release-mode worker
and prune regression to catch the failure that blocked nightly builds.

Co-Authored-By: Codex <noreply@openai.com>
2026-09-24 17:14:47 -04:00

130 lines
4 KiB
Rust

//! A `{{ secrets.NAME }}` reference resolves from the vault into a
//! command's environment, and the value never reaches `petri_records`:
//! Petri masks every record before it is appended.
//!
//! Built-in Host scopes run in process without a plugin executable.
mod support;
use std::collections::HashMap;
use std::sync::Arc;
use fabro_petri::SqliteRunStore;
use fabro_petri::check::Launch;
use fabro_petri::engine::{self, RunStatus};
use fabro_petri::runtime::RuntimeSpec;
use fabro_petri::secrets::VaultSecrets;
use fabro_store::test_support;
use fabro_types::SecretType;
use fabro_vault::Vault;
use support::{Silent, admit, no_questions, run_request};
const TOKEN: &str = "hunter2-hunter2-hunter2";
/// A command that checks the secret reached its environment and then
/// prints it, so the value would land in a log line if nothing masked it.
const WORKFLOW: &str = r#"digraph Secret {
graph [goal="Use a secret"]
start [shape=Mdiamond]
exit [shape=Msquare]
say [shape=parallelogram, script="test \"$TOKEN\" = hunter2-hunter2-hunter2 && echo \"token is $TOKEN\""]
start -> say -> exit
}"#;
const SETTINGS: &str = r#"_version = 1
[workflow]
graph = "workflow.fabro"
[run.environment]
id = "local"
[environments.local]
provider = "local"
[environments.local.env]
TOKEN = "{{ secrets.TOKEN }}"
"#;
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn a_secret_reaches_the_command_and_is_masked_in_every_record() {
let root = tempfile::tempdir().expect("a temp dir");
let pool = test_support::in_memory_pool_with(&[
fabro_db::BLOBS_MIGRATION_SQL,
fabro_db::PETRI_RECORDS_MIGRATION_SQL,
]);
let store = Arc::new(SqliteRunStore::new(pool.clone()));
let mut vault = Vault::from_entries(HashMap::new());
vault
.set("TOKEN", TOKEN, SecretType::Token, None)
.expect("a detached vault takes an entry");
let runtime = RuntimeSpec::default();
let graphs = admit(
&[("workflow.fabro", WORKFLOW), ("workflow.toml", SETTINGS)],
Launch::default(),
&runtime,
);
let mut request = run_request(
"secret",
&root.path().join("run"),
graphs,
store.clone(),
runtime,
no_questions(Arc::new(Silent)),
);
request.secrets = Some(Arc::new(VaultSecrets::from_vault(&vault)));
let outcome = engine::run(request).await.expect("the run ends");
assert_eq!(
outcome.status,
RunStatus::Success,
"the command saw the secret: {outcome:?}"
);
let records: Vec<String> = sqlx::query_scalar("SELECT record_json FROM petri_records")
.fetch_all(&pool)
.await
.expect("the records read");
assert!(!records.is_empty());
assert!(
records.iter().all(|record| !record.contains(TOKEN)),
"the secret's value is in a record"
);
assert!(
records.iter().any(|record| record.contains("token is ***")),
"the command's output was masked, not dropped"
);
}
/// Without a provider the reference resolves to nothing and the command
/// fails on the missing secret, as the standalone runner's does; the run
/// ends the way Fabro's failure policy for a command ends it.
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn a_secret_nobody_provides_fails_the_command() {
let root = tempfile::tempdir().expect("a temp dir");
let store = Arc::new(petri_store::MemoryRunStore::new());
let runtime = RuntimeSpec::default();
let graphs = admit(
&[("workflow.fabro", WORKFLOW), ("workflow.toml", SETTINGS)],
Launch::default(),
&runtime,
);
let request = run_request(
"unprovided",
&root.path().join("run"),
graphs,
store,
runtime,
no_questions(Arc::new(Silent)),
);
let outcome = engine::run(request).await.expect("the run ends");
assert!(
outcome
.failure
.as_deref()
.is_some_and(|failure| failure.contains("no secret named `TOKEN`")),
"{outcome:?}"
);
}