From d19031959d63b2e377837893cc497013ab56b56c Mon Sep 17 00:00:00 2001 From: Ben Schreiber Date: Mon, 25 May 2026 13:30:04 +0300 Subject: [PATCH 1/4] Dont fail fast on credential applying & increase update time to every 20 minutes --- k8s-agent/src/deployment_creds/mod.rs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/k8s-agent/src/deployment_creds/mod.rs b/k8s-agent/src/deployment_creds/mod.rs index c9eb0c4..4c194f1 100644 --- a/k8s-agent/src/deployment_creds/mod.rs +++ b/k8s-agent/src/deployment_creds/mod.rs @@ -1,6 +1,6 @@ use crate::{config::Config, k8s::tracker::K8S_TRACKER, task_runner::apply_secret}; use anyhow::Result; -use futures::future::try_join_all; +use futures::future::join_all; use maplit::btreemap; use platz_auth::{AccessToken, DEPLOYMENT_TOKEN_DURATION}; use platz_db::schema::deployment::Deployment; @@ -18,7 +18,7 @@ const REFRESH_CREDS_SLEEP_BETWEEN_CHUNKS: time::Duration = time::Duration::from_ #[tracing::instrument(err, skip_all, name = "d-creds")] pub async fn start(config: &Config) -> Result<()> { debug!("starting"); - let refresh_every = *DEPLOYMENT_TOKEN_DURATION / 2; + let refresh_every = *DEPLOYMENT_TOKEN_DURATION / 3; let mut interval = interval(refresh_every.to_std()?); let mut k8s_events_rx = K8S_TRACKER.outbound_notifications_rx().await; @@ -51,13 +51,15 @@ async fn refresh_credentials(config: &Config) -> Result<()> { .await? .chunks(REFRESH_CREDS_CHUNK_SIZE) { - try_join_all( + join_all( deploy_chunk .iter() .filter(|deployment| deployment.enabled) .map(|deployment| apply_deployment_credentials(deployment, &config.platz_url)), ) - .await?; + .await + .into_iter() + .collect::>>()?; time::sleep(REFRESH_CREDS_SLEEP_BETWEEN_CHUNKS).await; } From b5b7c10aea2f8b3304e9a1f02f5b5d79d4103950 Mon Sep 17 00:00:00 2001 From: Ben Schreiber Date: Mon, 25 May 2026 13:56:30 +0300 Subject: [PATCH 2/4] Expose deployment creds update frequency as an env var --- k8s-agent/src/config.rs | 9 +++++++++ k8s-agent/src/deployment_creds/mod.rs | 3 ++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/k8s-agent/src/config.rs b/k8s-agent/src/config.rs index 359271b..0e5ad12 100644 --- a/k8s-agent/src/config.rs +++ b/k8s-agent/src/config.rs @@ -22,6 +22,15 @@ pub struct Config { )] pub disable_deployment_credentials: bool, + /// How many times an hour to refresh deployment credentials. + #[arg( + long, + env = "PLATZ_DEPLOYMENT_CREDENTIALS_REFRESH_FREQUENCY", + default_value_t = 2, + value_parser = clap::value_parser!(i32).range(1..=60), + )] + pub deployment_credentials_refresh_frequency: i32, + #[arg(long, env = "PLATZ_OWN_URL")] pub platz_url: Url, } diff --git a/k8s-agent/src/deployment_creds/mod.rs b/k8s-agent/src/deployment_creds/mod.rs index 4c194f1..2bb8416 100644 --- a/k8s-agent/src/deployment_creds/mod.rs +++ b/k8s-agent/src/deployment_creds/mod.rs @@ -18,7 +18,8 @@ const REFRESH_CREDS_SLEEP_BETWEEN_CHUNKS: time::Duration = time::Duration::from_ #[tracing::instrument(err, skip_all, name = "d-creds")] pub async fn start(config: &Config) -> Result<()> { debug!("starting"); - let refresh_every = *DEPLOYMENT_TOKEN_DURATION / 3; + let refresh_every = + *DEPLOYMENT_TOKEN_DURATION / config.deployment_credentials_refresh_frequency; let mut interval = interval(refresh_every.to_std()?); let mut k8s_events_rx = K8S_TRACKER.outbound_notifications_rx().await; From bdc289800ea1f16711c4c677de4b4f71b689af0e Mon Sep 17 00:00:00 2001 From: Ben Schreiber Date: Mon, 25 May 2026 19:07:54 +0000 Subject: [PATCH 3/4] Address review: humantime refresh interval and default_value_t Use default_value_t for the disable_deployment_credentials flag, and express the deployment credentials refresh cadence as a humantime duration (PLATZ_DEPLOYMENT_CREDENTIALS_REFRESH_INTERVAL, default 20m) instead of a "times per hour" integer. Co-authored-by: popen2 Co-authored-by: Claude https://claude.ai/code/session_01UGtmhxw74pYTvYJ41tjrTT --- k8s-agent/src/config.rs | 11 +++++------ k8s-agent/src/deployment_creds/mod.rs | 7 +++---- 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/k8s-agent/src/config.rs b/k8s-agent/src/config.rs index 0e5ad12..5294d00 100644 --- a/k8s-agent/src/config.rs +++ b/k8s-agent/src/config.rs @@ -18,18 +18,17 @@ pub struct Config { #[arg( long, env = "PLATZ_DISABLE_DEPLOYMENT_CREDENTIALS", - default_value = "false" + default_value_t = false )] pub disable_deployment_credentials: bool, - /// How many times an hour to refresh deployment credentials. + /// How often to refresh deployment credentials. #[arg( long, - env = "PLATZ_DEPLOYMENT_CREDENTIALS_REFRESH_FREQUENCY", - default_value_t = 2, - value_parser = clap::value_parser!(i32).range(1..=60), + env = "PLATZ_DEPLOYMENT_CREDENTIALS_REFRESH_INTERVAL", + default_value = "20m" )] - pub deployment_credentials_refresh_frequency: i32, + pub deployment_credentials_refresh_interval: humantime::Duration, #[arg(long, env = "PLATZ_OWN_URL")] pub platz_url: Url, diff --git a/k8s-agent/src/deployment_creds/mod.rs b/k8s-agent/src/deployment_creds/mod.rs index 2bb8416..6349e69 100644 --- a/k8s-agent/src/deployment_creds/mod.rs +++ b/k8s-agent/src/deployment_creds/mod.rs @@ -2,7 +2,7 @@ use crate::{config::Config, k8s::tracker::K8S_TRACKER, task_runner::apply_secret use anyhow::Result; use futures::future::join_all; use maplit::btreemap; -use platz_auth::{AccessToken, DEPLOYMENT_TOKEN_DURATION}; +use platz_auth::AccessToken; use platz_db::schema::deployment::Deployment; use tokio::{ select, @@ -18,9 +18,8 @@ const REFRESH_CREDS_SLEEP_BETWEEN_CHUNKS: time::Duration = time::Duration::from_ #[tracing::instrument(err, skip_all, name = "d-creds")] pub async fn start(config: &Config) -> Result<()> { debug!("starting"); - let refresh_every = - *DEPLOYMENT_TOKEN_DURATION / config.deployment_credentials_refresh_frequency; - let mut interval = interval(refresh_every.to_std()?); + let refresh_every: time::Duration = config.deployment_credentials_refresh_interval.into(); + let mut interval = interval(refresh_every); let mut k8s_events_rx = K8S_TRACKER.outbound_notifications_rx().await; loop { From c6821cea83c548a3c9e3fc78f87d6a390cf54b9f Mon Sep 17 00:00:00 2001 From: Ben Schreiber Date: Mon, 25 May 2026 19:25:22 +0000 Subject: [PATCH 4/4] Make deployment credentials token duration configurable Add PLATZ_DEPLOYMENT_CREDENTIALS_TOKEN_DURATION (humantime, default 1h) and issue deployment tokens with that lifetime via AccessToken::for_deployment. At startup, validate that the refresh interval is non-zero and shorter than the token duration so credentials are always refreshed before they expire. The DEPLOYMENT_TOKEN_DURATION constant is no longer referenced and has been removed. Co-authored-by: popen2 Co-authored-by: Claude https://claude.ai/code/session_01UGtmhxw74pYTvYJ41tjrTT --- auth/src/access_token.rs | 17 ++++++-------- auth/src/lib.rs | 2 +- k8s-agent/src/config.rs | 14 +++++++++++ k8s-agent/src/deployment_creds/mod.rs | 23 ++++++++++++++++--- .../src/task_runner/install_and_upgrade.rs | 14 +++++++++-- 5 files changed, 54 insertions(+), 16 deletions(-) diff --git a/auth/src/access_token.rs b/auth/src/access_token.rs index db99be3..524e20b 100644 --- a/auth/src/access_token.rs +++ b/auth/src/access_token.rs @@ -14,7 +14,6 @@ const JWT_SECRET_BYTES: usize = 24; lazy_static::lazy_static! { pub static ref USER_TOKEN_DURATION: Duration = Duration::days(7); - pub static ref DEPLOYMENT_TOKEN_DURATION: Duration = Duration::hours(1); } pub(crate) async fn get_jwt_secret() -> Result, AuthError> { @@ -53,30 +52,28 @@ impl AccessToken { DateTime::from_timestamp(self.exp as i64, 0) .ok_or_else(|| AuthError::NaiveDateTimeConvertOverflow(self.exp)) } -} -impl From<&User> for AccessToken { - fn from(user: &User) -> Self { + pub fn for_deployment(deployment: &Deployment, duration: Duration) -> Self { let iat = chrono::Utc::now(); - let exp = iat + *USER_TOKEN_DURATION; + let exp = iat + duration; Self { iat: iat.timestamp() as usize, nbf: iat.timestamp() as usize, exp: exp.timestamp() as usize, - identity: user.into(), + identity: deployment.into(), } } } -impl From<&Deployment> for AccessToken { - fn from(deployment: &Deployment) -> Self { +impl From<&User> for AccessToken { + fn from(user: &User) -> Self { let iat = chrono::Utc::now(); - let exp = iat + *DEPLOYMENT_TOKEN_DURATION; + let exp = iat + *USER_TOKEN_DURATION; Self { iat: iat.timestamp() as usize, nbf: iat.timestamp() as usize, exp: exp.timestamp() as usize, - identity: deployment.into(), + identity: user.into(), } } } diff --git a/auth/src/lib.rs b/auth/src/lib.rs index 8c9e1b4..2ed7431 100644 --- a/auth/src/lib.rs +++ b/auth/src/lib.rs @@ -9,7 +9,7 @@ mod actix_traits; pub const API_TOKEN_HEADER: &str = "x-platz-token"; -pub use access_token::{AccessToken, DEPLOYMENT_TOKEN_DURATION, USER_TOKEN_DURATION}; +pub use access_token::{AccessToken, USER_TOKEN_DURATION}; pub use api_token::generate_api_token; pub use error::AuthError; pub use identity::ApiIdentity; diff --git a/k8s-agent/src/config.rs b/k8s-agent/src/config.rs index 5294d00..834a24b 100644 --- a/k8s-agent/src/config.rs +++ b/k8s-agent/src/config.rs @@ -1,3 +1,4 @@ +use anyhow::{Context, Result}; use url::Url; #[derive(clap::Parser)] @@ -30,6 +31,14 @@ pub struct Config { )] pub deployment_credentials_refresh_interval: humantime::Duration, + /// Lifetime of issued deployment credential tokens. + #[arg( + long, + env = "PLATZ_DEPLOYMENT_CREDENTIALS_TOKEN_DURATION", + default_value = "1h" + )] + pub deployment_credentials_token_duration: humantime::Duration, + #[arg(long, env = "PLATZ_OWN_URL")] pub platz_url: Url, } @@ -38,4 +47,9 @@ impl Config { pub fn should_refresh_deployment_credintials(&self) -> bool { !self.disable_deployment_credentials } + + pub fn deployment_token_duration(&self) -> Result { + chrono::Duration::from_std(self.deployment_credentials_token_duration.into()) + .context("PLATZ_DEPLOYMENT_CREDENTIALS_TOKEN_DURATION is out of range") + } } diff --git a/k8s-agent/src/deployment_creds/mod.rs b/k8s-agent/src/deployment_creds/mod.rs index 6349e69..f661d38 100644 --- a/k8s-agent/src/deployment_creds/mod.rs +++ b/k8s-agent/src/deployment_creds/mod.rs @@ -1,5 +1,5 @@ use crate::{config::Config, k8s::tracker::K8S_TRACKER, task_runner::apply_secret}; -use anyhow::Result; +use anyhow::{Result, bail}; use futures::future::join_all; use maplit::btreemap; use platz_auth::AccessToken; @@ -19,6 +19,19 @@ const REFRESH_CREDS_SLEEP_BETWEEN_CHUNKS: time::Duration = time::Duration::from_ pub async fn start(config: &Config) -> Result<()> { debug!("starting"); let refresh_every: time::Duration = config.deployment_credentials_refresh_interval.into(); + let token_duration: time::Duration = config.deployment_credentials_token_duration.into(); + if refresh_every.is_zero() { + bail!("PLATZ_DEPLOYMENT_CREDENTIALS_REFRESH_INTERVAL must be greater than zero"); + } + if refresh_every >= token_duration { + bail!( + "PLATZ_DEPLOYMENT_CREDENTIALS_REFRESH_INTERVAL ({}) must be shorter than \ + PLATZ_DEPLOYMENT_CREDENTIALS_TOKEN_DURATION ({}), otherwise credentials would \ + expire before being refreshed", + config.deployment_credentials_refresh_interval, + config.deployment_credentials_token_duration, + ); + } let mut interval = interval(refresh_every); let mut k8s_events_rx = K8S_TRACKER.outbound_notifications_rx().await; @@ -45,6 +58,7 @@ pub async fn start(config: &Config) -> Result<()> { async fn refresh_credentials(config: &Config) -> Result<()> { debug!("started"); + let token_duration = config.deployment_token_duration()?; let cluster_ids = K8S_TRACKER.get_ids().await; for deploy_chunk in Deployment::find_by_cluster_ids(cluster_ids) @@ -55,7 +69,9 @@ async fn refresh_credentials(config: &Config) -> Result<()> { deploy_chunk .iter() .filter(|deployment| deployment.enabled) - .map(|deployment| apply_deployment_credentials(deployment, &config.platz_url)), + .map(|deployment| { + apply_deployment_credentials(deployment, &config.platz_url, token_duration) + }), ) .await .into_iter() @@ -70,9 +86,10 @@ async fn refresh_credentials(config: &Config) -> Result<()> { pub(crate) async fn apply_deployment_credentials( deployment: &Deployment, platz_url: &Url, + token_duration: chrono::Duration, ) -> Result<()> { debug!("applying"); - let access_token = AccessToken::from(deployment); + let access_token = AccessToken::for_deployment(deployment, token_duration); apply_secret( deployment.cluster_id, &deployment.namespace_name().await?, diff --git a/k8s-agent/src/task_runner/install_and_upgrade.rs b/k8s-agent/src/task_runner/install_and_upgrade.rs index a5409a0..6d0d94f 100644 --- a/k8s-agent/src/task_runner/install_and_upgrade.rs +++ b/k8s-agent/src/task_runner/install_and_upgrade.rs @@ -36,7 +36,12 @@ impl RunnableDeploymentOperation for DeploymentInstallTask { deployment_to_namespace(deployment).await?, ) .await?; - apply_deployment_credentials(deployment, &config.platz_url).await?; + apply_deployment_credentials( + deployment, + &config.platz_url, + config.deployment_token_duration()?, + ) + .await?; match run_helm(config, "install", deployment, task).await { Ok(output) => { deployment.set_revision(Some(task.id)).await?; @@ -131,7 +136,12 @@ impl RunnableDeploymentOperation for DeploymentRecreaseTask { deployment_to_namespace(deployment).await?, ) .await?; - apply_deployment_credentials(deployment, &config.platz_url).await?; + apply_deployment_credentials( + deployment, + &config.platz_url, + config.deployment_token_duration()?, + ) + .await?; Ok("".to_owned()) } }