feat: configurable corpus base dir + Bascule dev config

- corpus_check_with_base(): accepts explicit base directory
- corpus_check(): still defaults to /opt/substrate/corpus
- Improved corpus test with actual Allowed/Denied assertions
- Updated bascule-dev.toml with [gsap] section and shell_command

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Tyler J King 2026-04-02 18:46:27 -04:00
parent fcc7758249
commit 5f7f9c0ff7
3 changed files with 27 additions and 7 deletions

View file

@ -6,8 +6,15 @@ ca_key_path = "/dev/null"
host_key_path = "/dev/null"
dispatch_mode = "direct"
auth_mode = "permissive"
shell_command = "/home/tking/.local/bin/gsh"
[elevation]
operator_ttl_secs = 3600
admin_ttl_secs = 1800
emergency_ttl_secs = 900
[gsap]
# broker_url = "http://localhost:8091"
# token = ""
default_corpus_cid = "sha256:dev-jumphost"
default_accord_template = "shell-exec"

View file

@ -15,15 +15,24 @@ pub enum CorpusCheckResult {
Denied { command: String, corpus_cid: String },
}
/// Default corpus base directory.
pub const DEFAULT_CORPUS_BASE: &str = "/opt/substrate/corpus";
/// Check if a command is authorized in the corpus directory.
///
/// `base_dir` overrides the default /opt/substrate/corpus (set via GSH_CORPUS_DIR env).
/// Returns Ok(result) always. Caller decides whether to block on Denied.
pub fn corpus_check(corpus_cid: &str, command: &str) -> CorpusCheckResult {
corpus_check_with_base(corpus_cid, command, DEFAULT_CORPUS_BASE)
}
/// corpus_check with an explicit base directory.
pub fn corpus_check_with_base(corpus_cid: &str, command: &str, base_dir: &str) -> CorpusCheckResult {
if corpus_cid == "sha256:ungoverned" {
return CorpusCheckResult::Ungoverned;
}
let corpus_dir = Path::new("/opt/substrate/corpus").join(corpus_cid);
let corpus_dir = Path::new(base_dir).join(corpus_cid);
if !corpus_dir.exists() {
return CorpusCheckResult::NotMounted;
}
@ -73,10 +82,14 @@ mod tests {
std::fs::create_dir_all(&corpus_dir).unwrap();
std::fs::write(corpus_dir.join("kubectl"), "").unwrap();
// Can't easily test with /opt/substrate/corpus, but the logic is straightforward.
// The unit test validates the command name extraction:
let cmd = "kubectl get pods -n test";
let name = cmd.split_whitespace().next().unwrap();
assert_eq!(name, "kubectl");
let base = dir.path().to_str().unwrap();
assert!(matches!(
corpus_check_with_base(cid, "kubectl get pods -n test", base),
CorpusCheckResult::Allowed
));
assert!(matches!(
corpus_check_with_base(cid, "helm install", base),
CorpusCheckResult::Denied { .. }
));
}
}

View file

@ -9,7 +9,7 @@ pub mod session;
pub use ac::{AcValidationError, AuthorizationContext};
pub use classifier::{classify_command, CommandClass, FREE_COMMANDS};
pub use config::GshConfig;
pub use corpus::{corpus_check, CorpusCheckResult};
pub use corpus::{corpus_check, corpus_check_with_base, CorpusCheckResult, DEFAULT_CORPUS_BASE};
pub use cr::{post_cr, CrResult};
pub use registry::ConsumedRegistry;
pub use session::SessionState;