use std::sync::Arc; use tonic::metadata::MetadataMap; use tonic::service::Interceptor as TonicInterceptor; use tonic::{Request, Status}; pub const RPC_KEY_HEADER: &str = "x-rpc-key"; pub const CALLER_SERVICE_HEADER: &str = "gitdata-service-id"; /// 门禁通过后挂到请求扩展上的元信息;只用于日志与审计。 #[derive(Debug, Clone)] pub struct RequestMeta { pub request_id: ::envelope::RequestId, pub caller_service: String, } impl RequestMeta { pub fn request_id(&self) -> Option<&str> { (!self.request_id.0.is_empty()).then_some(self.request_id.as_str()) } } pub fn request_id_from(metadata: &MetadataMap) -> Option { metadata .get(::envelope::REQUEST_ID_HEADER) .or_else(|| metadata.get(::envelope::LEGACY_REQUEST_ID_HEADER)) .and_then(|value| value.to_str().ok()) .and_then(::envelope::validate_request_id) } /// 共享密钥门禁:校验 `x-rpc-key`,通过后把 `RequestMeta` 挂到请求扩展。 #[derive(Clone)] pub struct Gate { key: Arc, } impl Gate { pub fn new(key: impl Into>) -> Self { Self { key: key.into() } } /// 空密钥会让所有请求通过,所以装配后必须先确认门禁真的能拒绝。 pub fn is_armed(&self) -> bool { !self.key.trim().is_empty() } } impl TonicInterceptor for Gate { fn call(&mut self, mut request: Request<()>) -> Result, Status> { let metadata = request.metadata().clone(); let request_id = ::envelope::context::request_id() .and_then(|value| ::envelope::validate_request_id(&value)) .or_else(|| request_id_from(&metadata)); if !self.is_armed() { return Err(unauthorized( "rpc gate is not configured", request_id.as_deref(), )); } let presented = metadata .get(RPC_KEY_HEADER) .and_then(|value| value.to_str().ok()) .map(str::trim) .unwrap_or_default(); if presented.is_empty() || !crypt::constant_time_eq(presented.as_bytes(), self.key.as_bytes()) { return Err(unauthorized( "rpc key is missing or invalid", request_id.as_deref(), )); } let meta = RequestMeta { request_id: ::envelope::RequestId( request_id.unwrap_or_else(::envelope::new_request_id), ), caller_service: metadata .get(CALLER_SERVICE_HEADER) .and_then(|value| value.to_str().ok()) .map(|value| value.trim().to_owned()) .filter(|value| !value.is_empty()) .unwrap_or_default(), }; request.extensions_mut().insert(meta); Ok(request) } } fn unauthorized(message: &str, request_id: Option<&str>) -> Status { let _ = request_id; Status::unauthenticated(message) } /// 读取门禁挂上的元信息;拦截器已校验密钥,这里只做扩展读取。 pub fn meta(request: &Request) -> Result { request .extensions() .get::() .cloned() .ok_or_else(|| Status::unauthenticated("rpc gate did not attach request metadata")) }