54 lines
4.1 KiB
Rust
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");
|
|
}
|