Skip to content

Commit

Permalink
fix: bootstrap reload in offline mode. (#595)
Browse files Browse the repository at this point in the history
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
  • Loading branch information
chriswk authored Dec 6, 2024
1 parent 2462bd2 commit 0dac00e
Show file tree
Hide file tree
Showing 5 changed files with 57 additions and 16 deletions.
4 changes: 0 additions & 4 deletions server/src/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -87,10 +87,6 @@ pub(crate) fn build_offline_mode(
let edge_tokens: Vec<EdgeToken> = 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<EdgeToken> = client_tokens
Expand Down
1 change: 0 additions & 1 deletion server/src/http/unleash_client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};

Expand Down
26 changes: 23 additions & 3 deletions server/src/internal_backstage.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,10 +65,16 @@ pub async fn ready(

#[get("/tokens")]
pub async fn tokens(
feature_refresher: web::Data<FeatureRefresher>,
token_validator: web::Data<TokenValidator>,
token_cache: web::Data<DashMap<String, EdgeToken>>,
feature_refresher: Option<web::Data<FeatureRefresher>>,
token_validator: Option<web::Data<TokenValidator>>,
) -> EdgeJsonResult<TokenInfo> {
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(
Expand Down Expand Up @@ -96,6 +102,18 @@ fn get_token_info(
}
}

fn get_offline_token_info(token_cache: web::Data<DashMap<String, EdgeToken>>) -> TokenInfo {
let edge_tokens: Vec<EdgeToken> = 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<MetricsCache>) -> EdgeJsonResult<MetricsInfo> {
let applications: Vec<ClientApplication> = metrics_cache
Expand Down Expand Up @@ -309,10 +327,12 @@ mod tests {
token_cache: Arc::new(DashMap::default()),
persistence: None,
};
let token_cache: DashMap<String, EdgeToken> = 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;
Expand Down
33 changes: 27 additions & 6 deletions server/src/middleware/validate_token.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<impl MessageBody + 'static>,
) -> Result<ServiceResponse<impl MessageBody>, actix_web::Error> {
trace!("Validating req: {}", req.path());
let maybe_validator = req.app_data::<Data<TokenValidator>>();
let token_cache = req
.app_data::<Data<DashMap<String, EdgeToken>>>()
Expand All @@ -27,18 +25,15 @@ 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())
.map_into_right_body()
}
}
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 {
Expand All @@ -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(),
Expand Down
9 changes: 7 additions & 2 deletions server/src/offline/offline_hotload.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,21 +23,26 @@ pub async fn start_hotload_loop(
engine_cache: Arc<DashMap<std::string::String, EngineState>>,
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<EdgeToken> = 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());
}
},
Expand Down

0 comments on commit 0dac00e

Please sign in to comment.