Files
sausage/host/tests/cred_security.rs
T

54 lines
4.1 KiB
Rust

//! Destination matching fails closed before and after a credential transform.
use sausage::cred::{*, inject::Outcome, items::Item, script::{self,Script,Limits}};
use serde_json::json;
struct Transform(serde_json::Value);
impl Script for Transform {
fn evaluate(&self,_:&str,_:&str,_:Option<&str>,_:Limits) -> std::result::Result<String,script::Error> { Ok(self.0.to_string()) }
}
fn config(host:&str,port:Option<u16>) -> Item {
serde_json::from_value(json!({"name":"destination","free_use":true,"rules":[{"scheme":"https","hosts":[host],"port":port,"path_prefixes":["/allowed/"],"methods":null}],"validate_js":null,"transform_js":"transform","fields":[]})).unwrap()
}
fn parts(uri:&str) -> http::request::Parts { http::Request::get(uri).body(()).unwrap().into_parts().0 }
#[test]
fn ports_before_and_after_transform() {
let dir = tempfile::tempdir().unwrap(); let c = Cred::open(dir.path()).unwrap(); let caller = Caller::default();
c.create_item(config("api.example",None),&caller,c.unlocked().unwrap()).unwrap();
let Outcome::Forward(p) = c.inject(parts("https://api.example:9999/allowed/"),&caller,&Transform(json!({"headers":[{"name":"x-injected","value":"synthetic"}]}))).unwrap() else { panic!("denied") };
assert!(!p.headers.contains_key("x-injected"));
assert!(c.inject(parts("https://api.example/allowed/"),&caller,&Transform(json!({"url":"https://api.example:9999/allowed/"}))).is_err());
}
#[test]
fn local_rules_require_ports_and_ipv6_matches() {
let dir = tempfile::tempdir().unwrap(); let c = Cred::open(dir.path()).unwrap(); let caller = Caller::default();
for host in ["localhost","127.0.0.1","::1","169.254.1.2","fe80::1"] { assert!(c.create_item(config(host,None),&caller,c.unlocked().unwrap()).is_err()); }
c.create_item(config("::1",Some(8443)),&caller,c.unlocked().unwrap()).unwrap();
let Outcome::Forward(p) = c.inject(parts("https://[::1]:8443/allowed/"),&caller,&Transform(json!({"headers":[{"name":"x-injected","value":"synthetic"}]}))).unwrap() else { panic!("denied") };
assert!(p.headers.contains_key("x-injected"));
}
#[test]
fn unsafe_paths_fail_before_and_after_transform() {
let dir = tempfile::tempdir().unwrap(); let c = Cred::open(dir.path()).unwrap(); let caller = Caller::default();
c.create_item(config("api.example",None),&caller,c.unlocked().unwrap()).unwrap();
for tail in ["./x","../x","%2e/x","%2E/x","x%2fy","x%2Fy","x%5cy","x%5Cy","x\\y"] {
let url = format!("https://api.example/allowed/{tail}");
assert!(c.inject(parts(&url),&caller,&Transform(json!({}))).is_err());
assert!(c.inject(parts("https://api.example/allowed/"),&caller,&Transform(json!({"url":url}))).is_err());
}
}
#[test]
fn transformed_authority_and_sensitive_headers() {
let dir = tempfile::tempdir().unwrap(); let c = Cred::open(dir.path()).unwrap(); let caller = Caller::default();
c.create_item(config("api.example",None),&caller,c.unlocked().unwrap()).unwrap();
let Outcome::Forward(p) = c.inject(parts("https://api.example/allowed/"),&caller,&Transform(json!({"headers":[{"name":"host","value":"wrong.example"},{"name":"authorization","value":"synthetic"}]}))).unwrap() else { panic!("denied") };
assert!(p.headers["authorization"].is_sensitive()); assert_eq!(p.headers["host"],"api.example");
assert!(c.inject(parts("https://api.example/allowed/"),&caller,&Transform(json!({"url":"https://user@api.example/allowed/"}))).is_err());
}
#[test]
fn oauth2_items_fail_without_exposing_refresh_values() {
let dir = tempfile::tempdir().unwrap(); let c = Cred::open(dir.path()).unwrap(); let caller = Caller::default(); let mut item = config("api.example",None);
item.fields.push(items::Field { field_id:String::new(),name:"refresh".into(),public:false,value:None,needs_value:true,field_type:types::FieldType::Oauth2RefreshToken,key_type:None });
let item = c.create_item(item,&caller,c.unlocked().unwrap()).unwrap();
c.set_field(&item.id,&item.fields[0].field_id,"synthetic",&caller,c.unlocked().unwrap()).unwrap();
assert_eq!(c.inject(parts("https://api.example/allowed/"),&caller,&Transform(json!({}))).err().unwrap().0,"oauth2_item");
}