better error handling for ip_is_authorized()
This commit is contained in:
parent
f3fc4924dc
commit
c32d12b5e0
@ -175,7 +175,7 @@ impl From<RpcSecretKey> for Uuid {
|
||||
}
|
||||
|
||||
impl Authorization {
|
||||
pub fn internal(db_conn: Option<DatabaseConnection>) -> anyhow::Result<Self> {
|
||||
pub fn internal(db_conn: Option<DatabaseConnection>) -> Web3ProxyResult<Self> {
|
||||
let authorization_checks = AuthorizationChecks {
|
||||
// any error logs on a local (internal) query are likely problems. log them all
|
||||
log_revert_chance: 1.0,
|
||||
@ -206,7 +206,7 @@ impl Authorization {
|
||||
proxy_mode: ProxyMode,
|
||||
referer: Option<Referer>,
|
||||
user_agent: Option<UserAgent>,
|
||||
) -> anyhow::Result<Self> {
|
||||
) -> Web3ProxyResult<Self> {
|
||||
// some origins can override max_requests_per_period for anon users
|
||||
let max_requests_per_period = origin
|
||||
.as_ref()
|
||||
@ -244,13 +244,13 @@ impl Authorization {
|
||||
referer: Option<Referer>,
|
||||
user_agent: Option<UserAgent>,
|
||||
authorization_type: AuthorizationType,
|
||||
) -> anyhow::Result<Self> {
|
||||
) -> Web3ProxyResult<Self> {
|
||||
// check ip
|
||||
match &authorization_checks.allowed_ips {
|
||||
None => {}
|
||||
Some(allowed_ips) => {
|
||||
if !allowed_ips.iter().any(|x| x.contains(&ip)) {
|
||||
return Err(anyhow::anyhow!("IP ({}) is not allowed!", ip));
|
||||
return Err(Web3ProxyError::IpNotAllowed(ip));
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -259,10 +259,10 @@ impl Authorization {
|
||||
match (&origin, &authorization_checks.allowed_origins) {
|
||||
(None, None) => {}
|
||||
(Some(_), None) => {}
|
||||
(None, Some(_)) => return Err(anyhow::anyhow!("Origin required")),
|
||||
(None, Some(_)) => return Err(Web3ProxyError::OriginRequired),
|
||||
(Some(origin), Some(allowed_origins)) => {
|
||||
if !allowed_origins.contains(origin) {
|
||||
return Err(anyhow::anyhow!("Origin ({}) is not allowed!", origin));
|
||||
return Err(Web3ProxyError::OriginNotAllowed(origin.clone()));
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -271,10 +271,10 @@ impl Authorization {
|
||||
match (&referer, &authorization_checks.allowed_referers) {
|
||||
(None, None) => {}
|
||||
(Some(_), None) => {}
|
||||
(None, Some(_)) => return Err(anyhow::anyhow!("Referer required")),
|
||||
(None, Some(_)) => return Err(Web3ProxyError::RefererRequired),
|
||||
(Some(referer), Some(allowed_referers)) => {
|
||||
if !allowed_referers.contains(referer) {
|
||||
return Err(anyhow::anyhow!("Referer ({:?}) is not allowed!", referer));
|
||||
return Err(Web3ProxyError::RefererNotAllowed(referer.clone()));
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -283,13 +283,10 @@ impl Authorization {
|
||||
match (&user_agent, &authorization_checks.allowed_user_agents) {
|
||||
(None, None) => {}
|
||||
(Some(_), None) => {}
|
||||
(None, Some(_)) => return Err(anyhow::anyhow!("User agent required")),
|
||||
(None, Some(_)) => return Err(Web3ProxyError::UserAgentRequired),
|
||||
(Some(user_agent), Some(allowed_user_agents)) => {
|
||||
if !allowed_user_agents.contains(user_agent) {
|
||||
return Err(anyhow::anyhow!(
|
||||
"User agent ({}) is not allowed!",
|
||||
user_agent
|
||||
));
|
||||
return Err(Web3ProxyError::UserAgentNotAllowed(user_agent.clone()));
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -451,7 +448,7 @@ pub async fn key_is_authorized(
|
||||
|
||||
impl Web3ProxyApp {
|
||||
/// Limit the number of concurrent requests from the given ip address.
|
||||
pub async fn ip_semaphore(&self, ip: IpAddr) -> anyhow::Result<Option<OwnedSemaphorePermit>> {
|
||||
pub async fn ip_semaphore(&self, ip: IpAddr) -> Web3ProxyResult<Option<OwnedSemaphorePermit>> {
|
||||
if let Some(max_concurrent_requests) = self.config.public_max_concurrent_requests {
|
||||
let semaphore = self
|
||||
.ip_semaphores
|
||||
@ -551,7 +548,7 @@ impl Web3ProxyApp {
|
||||
&self,
|
||||
ip: IpAddr,
|
||||
proxy_mode: ProxyMode,
|
||||
) -> anyhow::Result<RateLimitResult> {
|
||||
) -> Web3ProxyResult<RateLimitResult> {
|
||||
// TODO: dry this up with rate_limit_by_rpc_key?
|
||||
|
||||
// we don't care about user agent or origin or referer
|
||||
@ -608,7 +605,7 @@ impl Web3ProxyApp {
|
||||
ip: IpAddr,
|
||||
origin: Option<Origin>,
|
||||
proxy_mode: ProxyMode,
|
||||
) -> anyhow::Result<RateLimitResult> {
|
||||
) -> Web3ProxyResult<RateLimitResult> {
|
||||
// ip rate limits don't check referer or user agent
|
||||
// the do check origin because we can override rate limits for some origins
|
||||
let authorization = Authorization::external(
|
||||
@ -786,7 +783,7 @@ impl Web3ProxyApp {
|
||||
referer: Option<Referer>,
|
||||
rpc_key: RpcSecretKey,
|
||||
user_agent: Option<UserAgent>,
|
||||
) -> anyhow::Result<RateLimitResult> {
|
||||
) -> Web3ProxyResult<RateLimitResult> {
|
||||
let authorization_checks = self.authorization_checks(proxy_mode, rpc_key).await?;
|
||||
|
||||
// if no rpc_key_id matching the given rpc was found, then we can't rate limit by key
|
||||
|
@ -2,13 +2,16 @@
|
||||
|
||||
use super::authorization::Authorization;
|
||||
use crate::jsonrpc::JsonRpcForwardedResponse;
|
||||
|
||||
use std::net::IpAddr;
|
||||
|
||||
use axum::{
|
||||
headers,
|
||||
http::StatusCode,
|
||||
response::{IntoResponse, Response},
|
||||
Json,
|
||||
};
|
||||
use derive_more::From;
|
||||
use derive_more::{Display, Error, From};
|
||||
use http::header::InvalidHeaderValue;
|
||||
use ipnet::AddrParseError;
|
||||
use log::{debug, error, trace, warn};
|
||||
@ -22,10 +25,13 @@ pub type Web3ProxyResult<T> = Result<T, Web3ProxyError>;
|
||||
pub type Web3ProxyResponse = Web3ProxyResult<Response>;
|
||||
|
||||
// TODO:
|
||||
#[derive(Debug, From)]
|
||||
#[derive(Debug, Display, Error, From)]
|
||||
pub enum Web3ProxyError {
|
||||
AccessDenied,
|
||||
#[error(ignore)]
|
||||
Anyhow(anyhow::Error),
|
||||
#[error(ignore)]
|
||||
#[from(ignore)]
|
||||
BadRequest(String),
|
||||
SemaphoreAcquireError(AcquireError),
|
||||
Database(DbErr),
|
||||
@ -34,17 +40,34 @@ pub enum Web3ProxyError {
|
||||
InfluxDb2RequestError(influxdb2::RequestError),
|
||||
InvalidHeaderValue(InvalidHeaderValue),
|
||||
IpAddrParse(AddrParseError),
|
||||
#[error(ignore)]
|
||||
#[from(ignore)]
|
||||
IpNotAllowed(IpAddr),
|
||||
JoinError(JoinError),
|
||||
MsgPackEncode(rmp_serde::encode::Error),
|
||||
NotFound,
|
||||
OriginRequired,
|
||||
#[error(ignore)]
|
||||
#[from(ignore)]
|
||||
OriginNotAllowed(headers::Origin),
|
||||
#[display(fmt = "{:?}, {:?}", _0, _1)]
|
||||
RateLimited(Authorization, Option<Instant>),
|
||||
Redis(RedisError),
|
||||
RefererRequired,
|
||||
#[display(fmt = "{:?}", _0)]
|
||||
#[error(ignore)]
|
||||
#[from(ignore)]
|
||||
RefererNotAllowed(headers::Referer),
|
||||
/// simple way to return an error message to the user and an anyhow to our logs
|
||||
#[display(fmt = "{}, {}, {:?}", _0, _1, _2)]
|
||||
StatusCode(StatusCode, String, Option<anyhow::Error>),
|
||||
/// TODO: what should be attached to the timout?
|
||||
Timeout(tokio::time::error::Elapsed),
|
||||
UlidDecode(ulid::DecodeError),
|
||||
UnknownKey,
|
||||
UserAgentRequired,
|
||||
#[error(ignore)]
|
||||
UserAgentNotAllowed(headers::UserAgent),
|
||||
}
|
||||
|
||||
impl Web3ProxyError {
|
||||
@ -131,6 +154,17 @@ impl Web3ProxyError {
|
||||
),
|
||||
)
|
||||
}
|
||||
Self::IpNotAllowed(ip) => {
|
||||
warn!("IpNotAllowed ip={})", ip);
|
||||
(
|
||||
StatusCode::FORBIDDEN,
|
||||
JsonRpcForwardedResponse::from_string(
|
||||
format!("IP ({}) is not allowed!", ip),
|
||||
Some(StatusCode::FORBIDDEN.as_u16().into()),
|
||||
None,
|
||||
),
|
||||
)
|
||||
}
|
||||
Self::InvalidHeaderValue(err) => {
|
||||
warn!("InvalidHeaderValue err={:?}", err);
|
||||
(
|
||||
@ -184,6 +218,28 @@ impl Web3ProxyError {
|
||||
),
|
||||
)
|
||||
}
|
||||
Self::OriginRequired => {
|
||||
warn!("OriginRequired");
|
||||
(
|
||||
StatusCode::BAD_REQUEST,
|
||||
JsonRpcForwardedResponse::from_str(
|
||||
"Origin required",
|
||||
Some(StatusCode::BAD_REQUEST.as_u16().into()),
|
||||
None,
|
||||
),
|
||||
)
|
||||
}
|
||||
Self::OriginNotAllowed(origin) => {
|
||||
warn!("OriginNotAllowed origin={}", origin);
|
||||
(
|
||||
StatusCode::FORBIDDEN,
|
||||
JsonRpcForwardedResponse::from_string(
|
||||
format!("Origin ({}) is not allowed!", origin),
|
||||
Some(StatusCode::FORBIDDEN.as_u16().into()),
|
||||
None,
|
||||
),
|
||||
)
|
||||
}
|
||||
// TODO: this should actually by the id of the key. multiple users might control one key
|
||||
Self::RateLimited(authorization, retry_at) => {
|
||||
// TODO: emit a stat
|
||||
@ -227,6 +283,28 @@ impl Web3ProxyError {
|
||||
),
|
||||
)
|
||||
}
|
||||
Self::RefererRequired => {
|
||||
warn!("referer required");
|
||||
(
|
||||
StatusCode::BAD_REQUEST,
|
||||
JsonRpcForwardedResponse::from_str(
|
||||
"Referer required",
|
||||
Some(StatusCode::BAD_REQUEST.as_u16().into()),
|
||||
None,
|
||||
),
|
||||
)
|
||||
}
|
||||
Self::RefererNotAllowed(referer) => {
|
||||
warn!("referer not allowed referer={:?}", referer);
|
||||
(
|
||||
StatusCode::FORBIDDEN,
|
||||
JsonRpcForwardedResponse::from_string(
|
||||
format!("Referer ({:?}) is not allowed", referer),
|
||||
Some(StatusCode::FORBIDDEN.as_u16().into()),
|
||||
None,
|
||||
),
|
||||
)
|
||||
}
|
||||
Self::SemaphoreAcquireError(err) => {
|
||||
warn!("semaphore acquire err={:?}", err);
|
||||
(
|
||||
@ -293,6 +371,28 @@ impl Web3ProxyError {
|
||||
None,
|
||||
),
|
||||
),
|
||||
Self::UserAgentRequired => {
|
||||
warn!("UserAgentRequired");
|
||||
(
|
||||
StatusCode::BAD_REQUEST,
|
||||
JsonRpcForwardedResponse::from_str(
|
||||
"User agent required",
|
||||
Some(StatusCode::BAD_REQUEST.as_u16().into()),
|
||||
None,
|
||||
),
|
||||
)
|
||||
}
|
||||
Self::UserAgentNotAllowed(ua) => {
|
||||
warn!("UserAgentNotAllowed ua={}", ua);
|
||||
(
|
||||
StatusCode::FORBIDDEN,
|
||||
JsonRpcForwardedResponse::from_string(
|
||||
format!("User agent ({}) is not allowed!", ua),
|
||||
Some(StatusCode::FORBIDDEN.as_u16().into()),
|
||||
None,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1720,7 +1720,7 @@ mod tests {
|
||||
OpenRequestResult::Handle(_)
|
||||
));
|
||||
|
||||
let best_available_server_from_none = rpcs
|
||||
let _best_available_server_from_none = rpcs
|
||||
.best_available_rpc(&authorization, None, &[], None, None)
|
||||
.await;
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user