better error handling for ip_is_authorized()

This commit is contained in:
Rory Neithinger 2023-03-19 15:50:25 -07:00
parent f3fc4924dc
commit c32d12b5e0
3 changed files with 117 additions and 20 deletions

@ -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;