Threat intelligence preflight

Detect requests that contain submitted passwords and use a service to determine whether the password has leaked before allowing the request to proceed to origin (data from haveibeenpwned).

VCL

Use this solution in your VCL service (click RUN below to test this solution or clone it to make changes):

Compute@Edge

Use this solution in your Compute@Edge service:

  1. Rust
#[fastly::main]
fn main(mut req: Request) -> Result<Response, Error> {
log_fastly::init_simple("my_log", log::LevelFilter::Info);
let resp = match (req.get_method(), req.get_path()) {
(&Method::POST, "/post") => process_login(req)?,
_ => {
// Send request to httpbin.org, set proper host header
req.set_header(header::HOST, "httpbin.org");
req.send(BACKEND_HTTPBIN)?
}
};
Ok(resp)
}
/// Process login with threat check
fn process_login(mut req: Request) -> Result<Response, Error> {
// Get body as a string from the request
let body_string = req.take_body_str();
// Get credential from body string
let plain_cred = match sub_field(&body_string, "password", "&") {
Some("") | None => {
// No credential or empty credential found
log::info!("No valid credential is found");
return Ok(
Response::from_body("No credential found").with_status(StatusCode::NOT_FOUND)
);
}
Some(cred) => cred,
};
// Generate sha1 hash of credential
let threat_intel_key: String = hash_sha1(plain_cred);
// Split the hash of credential to left and right part at postion PREFIX_LENGTH
let (threat_intel_key_left, threat_intel_key_right) = threat_intel_key.split_at(PREFIX_LENGTH);
// Prepare the request for threat check
let threat_intel_url = format!(
"https://place-holder/threatIntelPOC?key={}",
threat_intel_key_left
);
log::info!(
"Checking for threat intelligence on credential '{}' using key {}",
plain_cred,
threat_intel_key,
);
let req_threat_check = Request::get(threat_intel_url)
.with_header("threat-intel-key", plain_cred)
.with_header(header::HOST, "us-central1-rd---product.cloudfunctions.net");
// Send threat check request to backend BACKEND_SECURITY_CHK
let resp = req_threat_check.send(BACKEND_SECURITY_CHK)?;
// Check if threat check result
let result = resp
.get_header("result")
.and_then(|h| h.to_str().ok())
.unwrap_or("");
let is_known_threat = result.contains(threat_intel_key_right);
log::info!(
"Credential is {} {} {}",
if is_known_threat {
"a known threat"
} else {
"OK"
},
result,
threat_intel_key_right
);
if is_known_threat {
// credential is a threat, bail out
return Ok(Response::from_body("Threat detected").with_status(StatusCode::NOT_FOUND));
}
// credential is not a threat, continue
req.set_body(body_string);
// Send request to httpbin.org, set proper host header
req.set_header(header::HOST, "httpbin.org");
Ok(req.send(BACKEND_HTTPBIN)?)
}