From 0dac00e039884bf74af98ab8ada171cd30e8eb6c Mon Sep 17 00:00:00 2001 From: Christopher Kolstad Date: Fri, 6 Dec 2024 13:43:01 +0100 Subject: [PATCH] fix: bootstrap reload in offline mode. (#595) After adding support for client and frontend tokens, we did not extend the reloader to check the client and frontend token arrays. This PR extends tokens with FE and Client tokens, to ensure that we refresh the data for all our tokens. In addition we make /internal-backstage/tokens useful for offline mode as well, to at least be able to see which tokens you added to Edge. Fixes: #594 --- server/src/builder.rs | 4 --- server/src/http/unleash_client.rs | 1 - server/src/internal_backstage.rs | 26 ++++++++++++++++--- server/src/middleware/validate_token.rs | 33 ++++++++++++++++++++----- server/src/offline/offline_hotload.rs | 9 +++++-- 5 files changed, 57 insertions(+), 16 deletions(-) diff --git a/server/src/builder.rs b/server/src/builder.rs index b5b0f773..46758328 100644 --- a/server/src/builder.rs +++ b/server/src/builder.rs @@ -87,10 +87,6 @@ pub(crate) fn build_offline_mode( let edge_tokens: Vec = tokens .iter() .map(|token| EdgeToken::from_str(token).unwrap_or_else(|_| EdgeToken::offline_token(token))) - .map(|mut token| { - token.token_type = Some(TokenType::Client); - token - }) .collect(); let edge_client_tokens: Vec = client_tokens diff --git a/server/src/http/unleash_client.rs b/server/src/http/unleash_client.rs index 631fdbbe..6397e842 100644 --- a/server/src/http/unleash_client.rs +++ b/server/src/http/unleash_client.rs @@ -500,7 +500,6 @@ mod tests { http::header::EntityTag, web, App, HttpResponse, }; - use capture_logger::{begin_capture, pop_captured}; use chrono::Duration; use unleash_types::client_features::{ClientFeature, ClientFeatures}; diff --git a/server/src/internal_backstage.rs b/server/src/internal_backstage.rs index af21a6aa..dc8bb76a 100644 --- a/server/src/internal_backstage.rs +++ b/server/src/internal_backstage.rs @@ -65,10 +65,16 @@ pub async fn ready( #[get("/tokens")] pub async fn tokens( - feature_refresher: web::Data, - token_validator: web::Data, + token_cache: web::Data>, + feature_refresher: Option>, + token_validator: Option>, ) -> EdgeJsonResult { - Ok(Json(get_token_info(feature_refresher, token_validator))) + match (feature_refresher, token_validator) { + (Some(feature_refresher), Some(token_validator)) => { + Ok(Json(get_token_info(feature_refresher, token_validator))) + } + _ => Ok(Json(get_offline_token_info(token_cache))), + } } fn get_token_info( @@ -96,6 +102,18 @@ fn get_token_info( } } +fn get_offline_token_info(token_cache: web::Data>) -> TokenInfo { + let edge_tokens: Vec = token_cache + .iter() + .map(|e| e.value().clone()) + .map(|t| crate::tokens::anonymize_token(&t)) + .collect(); + TokenInfo { + token_refreshes: vec![], + token_validation_status: edge_tokens, + } +} + #[get("/metricsbatch")] pub async fn metrics_batch(metrics_cache: web::Data) -> EdgeJsonResult { let applications: Vec = metrics_cache @@ -309,10 +327,12 @@ mod tests { token_cache: Arc::new(DashMap::default()), persistence: None, }; + let token_cache: DashMap = DashMap::default(); let app = test::init_service( App::new() .app_data(web::Data::new(feature_refresher)) .app_data(web::Data::new(token_validator)) + .app_data(web::Data::new(token_cache)) .service(web::scope("/internal-backstage").service(super::tokens)), ) .await; diff --git a/server/src/middleware/validate_token.rs b/server/src/middleware/validate_token.rs index c7f113ce..58838805 100644 --- a/server/src/middleware/validate_token.rs +++ b/server/src/middleware/validate_token.rs @@ -7,14 +7,12 @@ use actix_web::{ HttpResponse, }; use dashmap::DashMap; -use tracing::trace; pub async fn validate_token( token: EdgeToken, req: ServiceRequest, srv: crate::middleware::as_async_middleware::Next, ) -> Result, actix_web::Error> { - trace!("Validating req: {}", req.path()); let maybe_validator = req.app_data::>(); let token_cache = req .app_data::>>() @@ -27,10 +25,8 @@ pub async fn validate_token( let res = match known_token.status { TokenValidationStatus::Validated => match known_token.token_type { Some(TokenType::Frontend) => { - trace!("Got FE token validated {:?}", known_token); if req.path().contains("/api/frontend") || req.path().contains("/api/proxy") { - trace!("Was allowed to access"); srv.call(req).await?.map_into_left_body() } else { req.into_response(HttpResponse::Forbidden().finish()) @@ -38,7 +34,6 @@ pub async fn validate_token( } } Some(TokenType::Client) => { - trace!("Got Client token validated {:?}", known_token); if req.path().contains("/api/client") { srv.call(req).await?.map_into_left_body() } else { @@ -61,7 +56,33 @@ pub async fn validate_token( } None => { let res = match token_cache.get(&token.token) { - Some(_) => srv.call(req).await?.map_into_left_body(), + Some(t) => { + let token = t.value(); + match token.token_type { + Some(TokenType::Client) => { + if req.path().contains("/api/client") { + srv.call(req).await?.map_into_left_body() + } else { + req.into_response(HttpResponse::Forbidden().finish()) + .map_into_right_body() + } + } + Some(TokenType::Frontend) => { + if req.path().contains("/api/frontend") + || req.path().contains("/api/proxy") + { + srv.call(req).await?.map_into_left_body() + } else { + req.into_response(HttpResponse::Forbidden().finish()) + .map_into_right_body() + } + } + None => srv.call(req).await?.map_into_left_body(), + _ => req + .into_response(HttpResponse::Forbidden().finish()) + .map_into_right_body(), + } + } None => req .into_response(HttpResponse::Forbidden().finish()) .map_into_right_body(), diff --git a/server/src/offline/offline_hotload.rs b/server/src/offline/offline_hotload.rs index f40e56c0..45ce9a69 100644 --- a/server/src/offline/offline_hotload.rs +++ b/server/src/offline/offline_hotload.rs @@ -23,21 +23,26 @@ pub async fn start_hotload_loop( engine_cache: Arc>, offline_args: OfflineArgs, ) { - let known_tokens = offline_args.tokens; + let mut known_tokens = offline_args.tokens; + known_tokens.extend(offline_args.client_tokens); + known_tokens.extend(offline_args.frontend_tokens); let bootstrap_path = offline_args.bootstrap_file; loop { tokio::select! { _ = tokio::time::sleep(Duration::from_secs(offline_args.reload_interval)) => { let bootstrap = bootstrap_path.as_ref().map(|bootstrap_path|load_bootstrap(bootstrap_path)); + tracing::info!("Reloading bootstrap file"); match bootstrap { Some(Ok(bootstrap)) => { + tracing::info!("Found bootstrap file"); let edge_tokens: Vec = known_tokens .iter() .map(|token| EdgeToken::from_str(token).unwrap_or_else(|_| EdgeToken::offline_token(token))) .collect(); - + tracing::info!("Edge tokens: {:?}", edge_tokens); for edge_token in edge_tokens { + tracing::info!("Refreshing for {edge_token:?}"); load_offline_engine_cache(&edge_token, features_cache.clone(), engine_cache.clone(), bootstrap.clone()); } },