diff --git a/Cargo.lock b/Cargo.lock index 6937e1a..4278953 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -44,6 +44,21 @@ dependencies = [ "tracing", ] +[[package]] +name = "actix-cors" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9e772b3bcafe335042b5db010ab7c09013dad6eac4915c91d8d50902769f331" +dependencies = [ + "actix-utils", + "actix-web", + "derive_more 0.99.19", + "futures-util", + "log", + "once_cell", + "smallvec", +] + [[package]] name = "actix-http" version = "3.9.0" @@ -566,6 +581,7 @@ checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" name = "attestation-service" version = "0.1.0" dependencies = [ + "actix-cors", "actix-web", "aes-gcm", "aes-kw", diff --git a/attestation-service/Cargo.toml b/attestation-service/Cargo.toml index 848058c..8fd92dc 100644 --- a/attestation-service/Cargo.toml +++ b/attestation-service/Cargo.toml @@ -24,7 +24,7 @@ rvps-grpc = [ "prost", "tonic" ] grpc-bin = [ "clap", "env_logger", "prost", "tonic" ] # For restful CoCo-AS binary -restful-bin = [ "actix-web/openssl", "clap", "env_logger" ] +restful-bin = [ "actix-cors", "actix-web/openssl", "clap", "env_logger" ] [[bin]] name = "grpc-as" @@ -39,6 +39,10 @@ name = "attestation-challenge-client" path = "src/bin/attestation-challenge-client/main.rs" [dependencies] +# Pinned to 0.7.0: actix-cors 0.7.1 pulls in derive_more 2.x which requires +# rustc >= 1.81, while this fork's toolchain is 1.76. 0.7.0 uses derive_more +# 0.99 and builds fine. Bump once the MSRV is raised. +actix-cors = { version = "=0.7.0", optional = true } actix-web = { workspace = true, optional = true } aes-gcm = "0.10" aes-kw = "0.2" diff --git a/attestation-service/src/bin/restful-as.rs b/attestation-service/src/bin/restful-as.rs index 87b06d5..953902e 100644 --- a/attestation-service/src/bin/restful-as.rs +++ b/attestation-service/src/bin/restful-as.rs @@ -1,6 +1,7 @@ use std::{net::SocketAddr, path::Path, sync::Arc}; -use actix_web::{web, App, HttpServer}; +use actix_cors::Cors; +use actix_web::{http::header, web, App, HttpServer}; use anyhow::Result; use attestation_service::{config::Config, config::ConfigError, AttestationService, ServiceError}; use clap::{arg, command, Parser}; @@ -41,6 +42,12 @@ pub struct Cli { /// private key are provided then HTTPS will be enabled. #[arg(short = 'k', long)] pub https_prikey: Option, + + /// Allowed origin for CORS access (e.g., "http://localhost:3000"). + /// Can be specified multiple times or comma-separated. When empty + /// (the default), no CORS origins are allowed. + #[arg(short = 'r', long = "allowed_origin", value_delimiter = ',', num_args = 1..)] + pub allowed_origin: Vec, } #[derive(EnumString, AsRefStr)] @@ -87,6 +94,36 @@ pub enum RestfulError { Certificate(#[from] anyhow::Error), } +fn configure_cors(allowed_origin: &[String]) -> Cors { + let mut cors = Cors::default() + .allowed_methods(vec!["POST", "GET", "OPTIONS"]) + .allowed_headers(vec![header::CONTENT_TYPE, header::AUTHORIZATION]) + .max_age(86400); + + // Parse origin + if !allowed_origin.is_empty() { + let origins: Vec = allowed_origin + .iter() + .flat_map(|s| s.split(',')) + .map(|s| s.trim().to_string()) + .filter(|s| !s.is_empty()) + .collect(); + + for ori in &origins { + if ori.starts_with("http://") || ori.starts_with("https://") { + log::info!("Allowed CORS origin: {ori:?}"); + cors = cors.allowed_origin(ori.as_str()); + } else { + log::error!( + "Invalid CORS origin format: '{ori}'. Must start with http:// or https://" + ); + } + } + }; + + cors +} + #[actix_web::main] async fn main() -> Result<(), RestfulError> { env_logger::init_from_env(env_logger::Env::new().default_filter_or("info")); @@ -106,9 +143,12 @@ async fn main() -> Result<(), RestfulError> { let attestation_service = AttestationService::new(config).await?; + let allowed_origin = cli.allowed_origin.clone(); + let attestation_service = web::Data::new(Arc::new(RwLock::new(attestation_service))); let server = HttpServer::new(move || { App::new() + .wrap(configure_cors(&allowed_origin)) .service(web::resource(WebApi::Attestation.as_ref()).route(web::post().to(attestation))) .service( web::resource(WebApi::Policy.as_ref())