Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Scx1332/cors #3330

Open
wants to merge 8 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 21 additions & 6 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions core/serv-api/web/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,10 @@ ya-core-model = { workspace = true, features = ["appkey"] }
ya-service-api.workspace = true
ya-service-bus = { workspace = true }

actix-cors = "0.6"
actix-cors = "0.7"
actix-service = "2"
actix-web = "4"
actix-web-httpauth = "0.6"
actix-web-httpauth = "0.8"
anyhow = "1.0"
futures = "0.3"
log = "0.4"
Expand Down
2 changes: 1 addition & 1 deletion core/serv-api/web/examples/auth_middleware.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ async fn server() -> anyhow::Result<()> {
HttpServer::new(move || {
App::new()
.wrap(middleware::Logger::default())
.wrap(auth::Auth::new(cors.cache()))
.wrap(auth::Auth::new(cors.cache(), false))
.service(web::resource("/").route(web::get().to(response)))
})
.bind(rest_api_addr())?
Expand Down
29 changes: 29 additions & 0 deletions core/serv-api/web/src/middleware/allow_all_cors.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
use actix_web::http::header::HeaderMap;
use actix_web::http::header::HeaderName;
use actix_web::http::header::HeaderValue;
use std::str::FromStr;
use structopt::lazy_static::lazy_static;

#[rustfmt::skip]
fn get_full_permissive_headers() -> Vec<(&'static str, &'static str)> {
vec![
("Access-Control-Allow-Origin", "*"),
("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS"),
("Access-Control-Allow-Headers", "Content-Type, Authorization"),
("Access-Control-Allow-Credentials", "true"),
("Access-Control-Max-Age", "3600"),
]
}

pub fn add_full_allow_headers(header_map: &mut HeaderMap) {
lazy_static! {
static ref FULL_PERMISIVE_HEADERS: Vec<(&'static str, &'static str)> =
get_full_permissive_headers();
}
for (header_name, header_value) in FULL_PERMISIVE_HEADERS.iter() {
header_map.insert(
HeaderName::from_str(header_name).unwrap(),
HeaderValue::from_str(header_value).unwrap(),
);
}
}
36 changes: 31 additions & 5 deletions core/serv-api/web/src/middleware/auth/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,10 @@ pub mod resolver;
pub use crate::middleware::auth::ident::Identity;
pub use crate::middleware::auth::resolver::AppKeyCache;

use crate::middleware::allow_all_cors::add_full_allow_headers;
use actix_service::{Service, Transform};
use actix_web::dev::{ServiceRequest, ServiceResponse};
use actix_web::error::{Error, ErrorUnauthorized, ParseError};
use actix_web::error::{Error, InternalError, ParseError};
use actix_web::{web, HttpMessage};
use actix_web_httpauth::headers::authorization::{Bearer, Scheme};
use futures::future::{ok, Future, Ready};
Expand All @@ -19,11 +20,15 @@ use std::task::{Context, Poll};

pub struct Auth {
pub(crate) cache: AppKeyCache,
pub(crate) allow_cors_on_authentication_failure: bool,
}

impl Auth {
pub fn new(cache: AppKeyCache) -> Auth {
Auth { cache }
pub fn new(cache: AppKeyCache, allow_cors_on_authentication_failure: bool) -> Auth {
Auth {
cache,
allow_cors_on_authentication_failure,
}
}
}

Expand All @@ -43,13 +48,15 @@ where
ok(AuthMiddleware {
service: Rc::new(RefCell::new(service)),
cache: self.cache.clone(),
allow_cors_on_authentication_failure: self.allow_cors_on_authentication_failure,
})
}
}

pub struct AuthMiddleware<S> {
service: Rc<RefCell<S>>,
cache: AppKeyCache,
allow_cors_on_authentication_failure: bool,
}

impl<S, B> Service<ServiceRequest> for AuthMiddleware<S>
Expand Down Expand Up @@ -93,6 +100,7 @@ where
}
}

let allow_cors_on_failure = self.allow_cors_on_authentication_failure;
Box::pin(async move {
match header {
Some(key) => match cache.get_appkey(&key) {
Expand All @@ -107,12 +115,30 @@ where
req.method(),
req.path(),
);
Err(ErrorUnauthorized("Invalid application key"))

let mut res = actix_web::HttpResponse::Unauthorized().finish();
if allow_cors_on_failure {
add_full_allow_headers(res.headers_mut());
}

Err(actix_web::Error::from(InternalError::from_response(
"Invalid application key",
res,
)))
}
},
None => {
log::debug!("Missing application key");
Err(ErrorUnauthorized("Missing application key"))
let mut res = actix_web::HttpResponse::Unauthorized().finish();

if allow_cors_on_failure {
add_full_allow_headers(res.headers_mut());
}

Err(actix_web::Error::from(InternalError::from_response(
"Missing application key",
res,
)))
}
}
})
Expand Down
2 changes: 1 addition & 1 deletion core/serv-api/web/src/middleware/cors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ use crate::middleware::auth::resolver::AppKeyCache;
#[derive(Default, Clone, StructOpt, Debug)]
pub struct CorsConfig {
#[structopt(long = "api-allow-origin", env = "YAGNA_API_ALLOW_ORIGIN")]
allowed_origins: Vec<String>,
pub allowed_origins: Vec<String>,
/// Set a maximum time (in seconds) for which this CORS request may be cached.
#[structopt(long, default_value = "3600")]
max_age: usize,
Expand Down
1 change: 1 addition & 0 deletions core/serv-api/web/src/middleware/mod.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
mod allow_all_cors;
pub mod auth;
pub mod cors;

Expand Down
55 changes: 19 additions & 36 deletions core/serv/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
#![allow(clippy::obfuscated_if_else)]

use actix_web::{middleware, web, App, HttpResponse, HttpServer, Responder};
use actix_web::{web, HttpResponse, Responder};
use anyhow::{Context, Result};
use futures::prelude::*;
use metrics::{counter, gauge};
use metrics::gauge;
#[cfg(feature = "static-openssl")]
extern crate openssl_probe;

Expand Down Expand Up @@ -33,7 +33,7 @@ use ya_sb_proto::{DEFAULT_GSB_URL, GSB_URL_ENV_VAR};
use ya_service_api::{CliCtx, CommandOutput, ResponseTable};
use ya_service_api_interfaces::Provider;
use ya_service_api_web::{
middleware::{auth, cors::CorsConfig, Identity},
middleware::{cors::CorsConfig, Identity},
rest_api_host_port, DEFAULT_YAGNA_API_URL, YAGNA_API_URL_ENV_VAR,
};
use ya_sgx::SgxService;
Expand All @@ -47,10 +47,12 @@ use ya_service_bus::typed as gsb;
mod autocomplete;
mod extension;
mod model;
mod server;

use crate::extension::Extension;
use autocomplete::CompleteCommand;

use crate::server::standard::{create_server, CreateServerArgs};
use ya_activity::TrackerRef;
use ya_service_api_web::middleware::cors::AppKeyCors;

Expand Down Expand Up @@ -555,47 +557,28 @@ impl ServiceCommand {

let api_host_port = rest_api_host_port(api_url.clone());
let rest_address = api_host_port.clone();
let cors_on_auth_failure = cors.allowed_origins.contains(&"*".to_string());
if cors_on_auth_failure {
log::warn!("Running with CORS full permissive mode");
}
let cors = AppKeyCors::new(cors).await?;

let number_of_workers = env::var("YAGNA_HTTP_WORKERS")
.ok()
.and_then(|x| x.parse().ok())
.unwrap_or_else(num_cpus::get)
.clamp(1, 256);
let count_started = Arc::new(std::sync::atomic::AtomicUsize::new(0));
let server = HttpServer::new(move || {
let app = App::new()
.wrap(middleware::Logger::default())
.wrap(auth::Auth::new(cors.cache()))
.wrap(cors.cors())
.route("/dashboard", web::get().to(redirect_to_dashboard))
.route("/dashboard/{_:.*}", web::get().to(dashboard_serve))
.route("/me", web::get().to(me))
.service(forward_gsb);
let rest = Services::rest(app, &context);
if count_started.fetch_add(1, std::sync::atomic::Ordering::Relaxed)
== number_of_workers - 1
{
log::info!(
"All {} http workers started - listening on {}",
number_of_workers,
rest_address
);

counter!("yagna.service.up", 1);

tokio::task::spawn_local(async move {
ya_net::hybrid::send_bcast_new_neighbour().await
});
}
rest
})
.workers(number_of_workers)
// this is maximum supported timeout for our REST API
.keep_alive(std::time::Duration::from_secs(*max_rest_timeout))
.bind(api_host_port.clone())
.context(format!("Failed to bind http server on {:?}", api_host_port))?
.run();
let args = CreateServerArgs {
cors: Arc::new(cors),
cors_on_auth_failure,
context,
number_of_workers,
rest_address,
max_rest_timeout: *max_rest_timeout,
api_host_port,
};
let server = create_server(args)?;

let _ = extension::autostart(&ctx.data_dir, api_url, &ctx.gsb_url)
.await
Expand Down
1 change: 1 addition & 0 deletions core/serv/src/server.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
pub mod standard;
66 changes: 66 additions & 0 deletions core/serv/src/server/standard.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
use crate::{dashboard_serve, forward_gsb, me, redirect_to_dashboard, ServiceContext, Services};
use actix_web::{middleware, web, App, HttpServer};
use anyhow::Context;
use metrics::counter;
use std::sync::Arc;
use ya_service_api_web::middleware::auth;
use ya_service_api_web::middleware::cors::AppKeyCors;

pub struct CreateServerArgs {
pub cors: Arc<AppKeyCors>,
pub cors_on_auth_failure: bool,
pub context: ServiceContext,
pub number_of_workers: usize,
pub rest_address: String,
pub max_rest_timeout: u64,
pub api_host_port: String,
}

pub fn create_server(args: CreateServerArgs) -> anyhow::Result<actix_web::dev::Server> {
let count_started = Arc::new(std::sync::atomic::AtomicUsize::new(0));

let cors = args.cors;
let cors_on_auth_failure = args.cors_on_auth_failure;
let number_of_workers = args.number_of_workers;
let rest_address = args.rest_address;
let context = args.context;
Ok(HttpServer::new(move || {
let app = App::new()
.wrap(middleware::Logger::default())
.wrap(auth::Auth::new(cors.cache(), cors_on_auth_failure))
.wrap(cors.cors())
.route("/dashboard", web::get().to(redirect_to_dashboard))
.route("/dashboard/{_:.*}", web::get().to(dashboard_serve))
.route("/me", web::get().to(me))
.service(forward_gsb);
let rest = Services::rest(app, &context);
if count_started.fetch_add(1, std::sync::atomic::Ordering::Relaxed) == number_of_workers - 1
{
log::info!(
"All {} http workers started - listening on {}",
number_of_workers,
rest_address
);

if cors_on_auth_failure {
log::info!("CORS allow origins headers will be added on auth failure");
}

counter!("yagna.service.up", 1);

tokio::task::spawn_local(
async move { ya_net::hybrid::send_bcast_new_neighbour().await },
);
}
rest
})
.workers(args.number_of_workers)
// this is maximum supported timeout for our REST API
.keep_alive(std::time::Duration::from_secs(args.max_rest_timeout))
.bind(args.api_host_port.clone())
.context(format!(
"Failed to bind http server on {:?}",
args.api_host_port
))?
.run())
}
2 changes: 1 addition & 1 deletion test-utils/test-framework/framework-mocks/src/node.rs
Original file line number Diff line number Diff line change
Expand Up @@ -214,7 +214,7 @@ impl MockNode {
let srv = HttpServer::new(move || {
App::new()
.wrap(middleware::Logger::default())
.wrap(auth::Auth::new(cors.cache()))
.wrap(auth::Auth::new(cors.cache(), true))
.wrap(cors.cors())
.service(
payments
Expand Down
Loading