From e97ee22d0f4a02e7c82cd7aee89dec18e2661a83 Mon Sep 17 00:00:00 2001 From: Ruediger Birkner Date: Thu, 8 Aug 2024 17:50:00 +0200 Subject: [PATCH 1/2] add headers to content-type, x-content-type-options, and x-frame-options headers to api calls --- src/routing/proxy.rs | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/src/routing/proxy.rs b/src/routing/proxy.rs index 4b5c30a..5c16a6e 100644 --- a/src/routing/proxy.rs +++ b/src/routing/proxy.rs @@ -11,6 +11,7 @@ use axum::{ }; use candid::Principal; use derive_new::new; +use http::header::{HeaderValue, CONTENT_TYPE, X_CONTENT_TYPE_OPTIONS, X_FRAME_OPTIONS}; use ic_agent::agent::http_transport::route_provider::RouteProvider; use regex::Regex; use url::Url; @@ -22,6 +23,16 @@ lazy_static::lazy_static! { pub static ref REGEX_REG_ID: Regex = Regex::new(r"^[a-zA-Z0-9]+$").unwrap(); } +// Clippy complains that these are interior-mutable. +// We don't mutate them, so silence it. +// https://rust-lang.github.io/rust-clippy/master/index.html#/declare_interior_mutable_const +#[allow(clippy::declare_interior_mutable_const)] +const CONTENT_TYPE_CBOR: HeaderValue = HeaderValue::from_static("application/cbor"); +#[allow(clippy::declare_interior_mutable_const)] +const X_CONTENT_TYPE_OPTIONS_NO_SNIFF: HeaderValue = HeaderValue::from_static("nosniff"); +#[allow(clippy::declare_interior_mutable_const)] +const X_FRAME_OPTIONS_DENY: HeaderValue = HeaderValue::from_static("DENY"); + // Proxies provided Axum request to a given URL using Reqwest Client trait object and returns Axum response async fn proxy( url: Url, @@ -56,7 +67,7 @@ pub struct ApiProxyState { route_provider: Arc, } -// Proxies /api/v2/... endpoints to the IC +// Proxies /api/v2/... and /api/v3/... endpoints to the IC pub async fn api_proxy( State(state): State>, OriginalUri(original_uri): OriginalUri, @@ -85,6 +96,20 @@ pub async fn api_proxy( .await .map_err(ErrorCause::from)?; + // Set the correct content-type for all replies if it's not an error + // The replica and the API boundary nodes should set these headers. This is just for redundancy. + if response.status().is_success() { + response + .headers_mut() + .insert(CONTENT_TYPE, CONTENT_TYPE_CBOR); + response + .headers_mut() + .insert(X_CONTENT_TYPE_OPTIONS, X_CONTENT_TYPE_OPTIONS_NO_SNIFF); + response + .headers_mut() + .insert(X_FRAME_OPTIONS, X_FRAME_OPTIONS_DENY); + } + let bn_metadata = BNResponseMetadata::from(response.headers_mut()); response.extensions_mut().insert(bn_metadata); response.extensions_mut().insert(matched_path); From 738ed3aacfddf3fc715ae6dc889fd8b07e3fa873 Mon Sep 17 00:00:00 2001 From: Ruediger Birkner Date: Fri, 9 Aug 2024 11:34:28 +0200 Subject: [PATCH 2/2] move header values to separate file --- src/http/headers.rs | 32 ++++++++++++++++++++++++++++ src/http/mod.rs | 1 + src/metrics/vector.rs | 5 +---- src/routing/ic/handler.rs | 21 +++++++++--------- src/routing/ic/mod.rs | 28 ++++++++++-------------- src/routing/ic/transport.rs | 3 +-- src/routing/middleware/cors.rs | 3 +-- src/routing/middleware/headers.rs | 15 +++++++------ src/routing/middleware/mod.rs | 5 ----- src/routing/middleware/request_id.rs | 2 +- src/routing/proxy.rs | 17 +++++---------- 11 files changed, 73 insertions(+), 59 deletions(-) create mode 100644 src/http/headers.rs diff --git a/src/http/headers.rs b/src/http/headers.rs new file mode 100644 index 0000000..f3a947e --- /dev/null +++ b/src/http/headers.rs @@ -0,0 +1,32 @@ +// Clippy complains that these are interior-mutable. +// We don't mutate them, so silence it. +// https://rust-lang.github.io/rust-clippy/master/index.html#/declare_interior_mutable_const +#![allow(clippy::declare_interior_mutable_const)] +#![allow(clippy::borrow_interior_mutable_const)] + +use http::header::{HeaderName, HeaderValue}; + +pub const HEADER_IC_CACHE_STATUS: HeaderName = HeaderName::from_static("x-ic-cache-status"); +pub const HEADER_IC_CACHE_BYPASS_REASON: HeaderName = + HeaderName::from_static("x-ic-cache-bypass-reason"); +pub const HEADER_IC_SUBNET_ID: HeaderName = HeaderName::from_static("x-ic-subnet-id"); +pub const HEADER_IC_NODE_ID: HeaderName = HeaderName::from_static("x-ic-node-id"); +pub const HEADER_IC_SUBNET_TYPE: HeaderName = HeaderName::from_static("x-ic-subnet-type"); +pub const HEADER_IC_CANISTER_ID_CBOR: HeaderName = HeaderName::from_static("x-ic-canister-id-cbor"); +pub const HEADER_IC_METHOD_NAME: HeaderName = HeaderName::from_static("x-ic-method-name"); +pub const HEADER_IC_SENDER: HeaderName = HeaderName::from_static("x-ic-sender"); +pub const HEADER_IC_RETRIES: HeaderName = HeaderName::from_static("x-ic-retries"); +pub const HEADER_IC_ERROR_CAUSE: HeaderName = HeaderName::from_static("x-ic-error-cause"); +pub const HEADER_IC_REQUEST_TYPE: HeaderName = HeaderName::from_static("x-ic-request-type"); +pub const HEADER_IC_CANISTER_ID: HeaderName = HeaderName::from_static("x-ic-canister-id"); +pub const HEADER_IC_COUNTRY_CODE: HeaderName = HeaderName::from_static("x-ic-country-code"); +pub const X_REQUEST_ID: HeaderName = HeaderName::from_static("x-request-id"); +pub const X_REQUESTED_WITH: HeaderName = HeaderName::from_static("x-requested-with"); + +pub const CONTENT_TYPE_CBOR: HeaderValue = HeaderValue::from_static("application/cbor"); +pub const CONTENT_TYPE_OCTET_STREAM: HeaderValue = + HeaderValue::from_static("application/octet-stream"); +pub const HEADER_HSTS: HeaderValue = + HeaderValue::from_static("max-age=31536000; includeSubDomains"); +pub const X_CONTENT_TYPE_OPTIONS_NO_SNIFF: HeaderValue = HeaderValue::from_static("nosniff"); +pub const X_FRAME_OPTIONS_DENY: HeaderValue = HeaderValue::from_static("DENY"); diff --git a/src/http/mod.rs b/src/http/mod.rs index af075a9..02af54b 100644 --- a/src/http/mod.rs +++ b/src/http/mod.rs @@ -1,5 +1,6 @@ pub mod client; pub mod dns; +pub mod headers; pub mod server; use std::{ diff --git a/src/metrics/vector.rs b/src/metrics/vector.rs index 1995459..8b763f4 100644 --- a/src/metrics/vector.rs +++ b/src/metrics/vector.rs @@ -20,10 +20,7 @@ use tracing::warn; use url::Url; use vector_lib::{codecs::encoding::NativeSerializer, config::LogNamespace, event::Event}; -use crate::{cli, http}; - -#[allow(clippy::declare_interior_mutable_const)] -const CONTENT_TYPE_OCTET_STREAM: HeaderValue = HeaderValue::from_static("application/octet-stream"); +use crate::{cli, http, http::headers::CONTENT_TYPE_OCTET_STREAM}; /// Encodes Vector events into a native format with length delimiting #[derive(Clone)] diff --git a/src/routing/ic/handler.rs b/src/routing/ic/handler.rs index cbd4a06..ed5b2be 100644 --- a/src/routing/ic/handler.rs +++ b/src/routing/ic/handler.rs @@ -10,14 +10,17 @@ use http::HeaderValue; use http_body_util::{BodyExt, LengthLimitError, Limited}; use ic_http_gateway::{CanisterRequest, HttpGatewayClient, HttpGatewayRequestArgs}; -use crate::routing::{ - error_cause::ErrorCause, - ic::{ - transport::{PassHeaders, PASS_HEADERS}, - IcResponseStatus, +use crate::{ + http::headers::X_REQUEST_ID, + routing::{ + error_cause::ErrorCause, + ic::{ + transport::{PassHeaders, PASS_HEADERS}, + IcResponseStatus, + }, + middleware::request_id::RequestId, + CanisterId, RequestCtx, }, - middleware::{self, request_id::RequestId}, - CanisterId, RequestCtx, }; use super::BNResponseMetadata; @@ -70,9 +73,7 @@ pub async fn handler( let hdr = HeaderValue::from_maybe_shared(Bytes::from(request_id.to_string())).unwrap(); - x.borrow_mut() - .headers_out - .insert(middleware::X_REQUEST_ID, hdr) + x.borrow_mut().headers_out.insert(X_REQUEST_ID, hdr) }); // Execute the request diff --git a/src/routing/ic/mod.rs b/src/routing/ic/mod.rs index 329815e..67848c7 100644 --- a/src/routing/ic/mod.rs +++ b/src/routing/ic/mod.rs @@ -14,23 +14,17 @@ use http_body_util::Either; use ic_agent::agent::http_transport::route_provider::RouteProvider; use ic_http_gateway::{HttpGatewayClient, HttpGatewayResponse, HttpGatewayResponseMetadata}; -use crate::{http::Client as HttpClient, Cli}; - -pub const HEADER_IC_CACHE_STATUS: HeaderName = HeaderName::from_static("x-ic-cache-status"); -pub const HEADER_IC_CACHE_BYPASS_REASON: HeaderName = - HeaderName::from_static("x-ic-cache-bypass-reason"); -pub const HEADER_IC_SUBNET_ID: HeaderName = HeaderName::from_static("x-ic-subnet-id"); -pub const HEADER_IC_NODE_ID: HeaderName = HeaderName::from_static("x-ic-node-id"); -pub const HEADER_IC_SUBNET_TYPE: HeaderName = HeaderName::from_static("x-ic-subnet-type"); -pub const HEADER_IC_CANISTER_ID_CBOR: HeaderName = HeaderName::from_static("x-ic-canister-id-cbor"); -pub const HEADER_IC_METHOD_NAME: HeaderName = HeaderName::from_static("x-ic-method-name"); -pub const HEADER_IC_SENDER: HeaderName = HeaderName::from_static("x-ic-sender"); -pub const HEADER_IC_RETRIES: HeaderName = HeaderName::from_static("x-ic-retries"); -pub const HEADER_IC_ERROR_CAUSE: HeaderName = HeaderName::from_static("x-ic-error-cause"); -pub const HEADER_IC_REQUEST_TYPE: HeaderName = HeaderName::from_static("x-ic-request-type"); -pub const HEADER_IC_CANISTER_ID: HeaderName = HeaderName::from_static("x-ic-canister-id"); -pub const HEADER_IC_COUNTRY_CODE: http::HeaderName = - http::HeaderName::from_static("x-ic-country-code"); +use crate::{ + http::{ + headers::{ + HEADER_IC_CACHE_BYPASS_REASON, HEADER_IC_CACHE_STATUS, HEADER_IC_CANISTER_ID_CBOR, + HEADER_IC_ERROR_CAUSE, HEADER_IC_METHOD_NAME, HEADER_IC_NODE_ID, HEADER_IC_RETRIES, + HEADER_IC_SENDER, HEADER_IC_SUBNET_ID, HEADER_IC_SUBNET_TYPE, + }, + Client as HttpClient, + }, + Cli, +}; /// Metadata about the request by a Boundary Node (ic-boundary) #[derive(Clone)] diff --git a/src/routing/ic/transport.rs b/src/routing/ic/transport.rs index 328a381..019996c 100644 --- a/src/routing/ic/transport.rs +++ b/src/routing/ic/transport.rs @@ -22,11 +22,10 @@ use reqwest::{ use tokio::task_local; use url::Url; -use crate::http::Client as HttpClient; +use crate::http::{headers::CONTENT_TYPE_CBOR, Client as HttpClient}; type AgentFuture<'a, V> = Pin> + Send + 'a>>; -const CONTENT_TYPE_CBOR: HeaderValue = HeaderValue::from_static("application/cbor"); const MAX_RESPONSE_SIZE: usize = 2 * 1_048_576; pub struct PassHeaders { diff --git a/src/routing/middleware/cors.rs b/src/routing/middleware/cors.rs index 2791b5b..162bdda 100644 --- a/src/routing/middleware/cors.rs +++ b/src/routing/middleware/cors.rs @@ -11,8 +11,7 @@ use http::{ }; use tower_http::cors::{Any, CorsLayer}; -use super::{X_REQUESTED_WITH, X_REQUEST_ID}; -use crate::routing::ic::HEADER_IC_CANISTER_ID; +use crate::http::headers::{HEADER_IC_CANISTER_ID, X_REQUESTED_WITH, X_REQUEST_ID}; const MINUTE: Duration = Duration::from_secs(60); diff --git a/src/routing/middleware/headers.rs b/src/routing/middleware/headers.rs index c5fad7a..2beb5b4 100644 --- a/src/routing/middleware/headers.rs +++ b/src/routing/middleware/headers.rs @@ -1,13 +1,16 @@ -#![allow(clippy::declare_interior_mutable_const)] -#![allow(clippy::borrow_interior_mutable_const)] - use axum::{extract::Request, middleware::Next, response::Response}; use bytes::Bytes; use http::header::{HeaderName, HeaderValue, STRICT_TRANSPORT_SECURITY}; -use crate::routing::{ic::*, CanisterId}; - -const HEADER_HSTS: HeaderValue = HeaderValue::from_static("max-age=31536000; includeSubDomains"); +use crate::{ + http::headers::{ + HEADER_HSTS, HEADER_IC_CACHE_BYPASS_REASON, HEADER_IC_CACHE_STATUS, HEADER_IC_CANISTER_ID, + HEADER_IC_CANISTER_ID_CBOR, HEADER_IC_COUNTRY_CODE, HEADER_IC_ERROR_CAUSE, + HEADER_IC_METHOD_NAME, HEADER_IC_NODE_ID, HEADER_IC_REQUEST_TYPE, HEADER_IC_RETRIES, + HEADER_IC_SENDER, HEADER_IC_SUBNET_ID, HEADER_IC_SUBNET_TYPE, + }, + routing::CanisterId, +}; const HEADERS_REMOVE: [HeaderName; 12] = [ HEADER_IC_CACHE_BYPASS_REASON, diff --git a/src/routing/middleware/mod.rs b/src/routing/middleware/mod.rs index 9c9de40..f6c5d37 100644 --- a/src/routing/middleware/mod.rs +++ b/src/routing/middleware/mod.rs @@ -12,11 +12,6 @@ use std::str::FromStr; use axum::extract::Request; use fqdn::FQDN; -use http::HeaderName; - -// Headers -pub const X_REQUEST_ID: HeaderName = HeaderName::from_static("x-request-id"); -pub const X_REQUESTED_WITH: HeaderName = HeaderName::from_static("x-requested-with"); // Attempts to extract host from HTTP2 "authority" pseudo-header or from HTTP/1.1 "Host" header fn extract_authority(request: &Request) -> Option { diff --git a/src/routing/middleware/request_id.rs b/src/routing/middleware/request_id.rs index d42174d..9732011 100644 --- a/src/routing/middleware/request_id.rs +++ b/src/routing/middleware/request_id.rs @@ -5,7 +5,7 @@ use bytes::Bytes; use http::header::HeaderValue; use uuid::Uuid; -use super::X_REQUEST_ID; +use crate::http::headers::X_REQUEST_ID; #[derive(Clone, Copy)] pub struct RequestId(pub Uuid); diff --git a/src/routing/proxy.rs b/src/routing/proxy.rs index 5c16a6e..44944c1 100644 --- a/src/routing/proxy.rs +++ b/src/routing/proxy.rs @@ -11,28 +11,21 @@ use axum::{ }; use candid::Principal; use derive_new::new; -use http::header::{HeaderValue, CONTENT_TYPE, X_CONTENT_TYPE_OPTIONS, X_FRAME_OPTIONS}; +use http::header::{CONTENT_TYPE, X_CONTENT_TYPE_OPTIONS, X_FRAME_OPTIONS}; use ic_agent::agent::http_transport::route_provider::RouteProvider; use regex::Regex; use url::Url; use super::{body, error_cause::ErrorCause, ic::BNResponseMetadata}; -use crate::http::Client; +use crate::http::{ + headers::{CONTENT_TYPE_CBOR, X_CONTENT_TYPE_OPTIONS_NO_SNIFF, X_FRAME_OPTIONS_DENY}, + Client, +}; lazy_static::lazy_static! { pub static ref REGEX_REG_ID: Regex = Regex::new(r"^[a-zA-Z0-9]+$").unwrap(); } -// Clippy complains that these are interior-mutable. -// We don't mutate them, so silence it. -// https://rust-lang.github.io/rust-clippy/master/index.html#/declare_interior_mutable_const -#[allow(clippy::declare_interior_mutable_const)] -const CONTENT_TYPE_CBOR: HeaderValue = HeaderValue::from_static("application/cbor"); -#[allow(clippy::declare_interior_mutable_const)] -const X_CONTENT_TYPE_OPTIONS_NO_SNIFF: HeaderValue = HeaderValue::from_static("nosniff"); -#[allow(clippy::declare_interior_mutable_const)] -const X_FRAME_OPTIONS_DENY: HeaderValue = HeaderValue::from_static("DENY"); - // Proxies provided Axum request to a given URL using Reqwest Client trait object and returns Axum response async fn proxy( url: Url,