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