From b237aed906272cb83a169f5fefd32551d42e91a2 Mon Sep 17 00:00:00 2001 From: hugocasa Date: Wed, 2 Sep 2026 12:31:58 +0200 Subject: [PATCH] fix: gate credential maintenance on enterprise and alert on stalled renewal --- ...0ecdbda3fe30cc50d68dc5dfd357113f412ac.json | 23 +++++++++++++++++++ backend/ee-repo-ref.txt | 2 +- backend/src/monitor.rs | 9 ++++---- .../git_sync/GitSyncRepositoryCard.svelte | 13 ++++++++++- 4 files changed, 40 insertions(+), 7 deletions(-) create mode 100644 backend/.sqlx/query-55a272a0050115f90b4fd0de1350ecdbda3fe30cc50d68dc5dfd357113f412ac.json diff --git a/backend/.sqlx/query-55a272a0050115f90b4fd0de1350ecdbda3fe30cc50d68dc5dfd357113f412ac.json b/backend/.sqlx/query-55a272a0050115f90b4fd0de1350ecdbda3fe30cc50d68dc5dfd357113f412ac.json new file mode 100644 index 0000000000..da8ccd58ce --- /dev/null +++ b/backend/.sqlx/query-55a272a0050115f90b4fd0de1350ecdbda3fe30cc50d68dc5dfd357113f412ac.json @@ -0,0 +1,23 @@ +{ + "db_name": "PostgreSQL", + "query": "\n SELECT elem->'credential'\n FROM workspace_settings, jsonb_array_elements(git_sync->'repositories') AS elem\n WHERE workspace_id = $1 AND elem->>'git_repo_resource_path' = $2\n ", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "?column?", + "type_info": "Jsonb" + } + ], + "parameters": { + "Left": [ + "Text", + "Text" + ] + }, + "nullable": [ + null + ] + }, + "hash": "55a272a0050115f90b4fd0de1350ecdbda3fe30cc50d68dc5dfd357113f412ac" +} diff --git a/backend/ee-repo-ref.txt b/backend/ee-repo-ref.txt index 0641bed8dd..496498d1f5 100644 --- a/backend/ee-repo-ref.txt +++ b/backend/ee-repo-ref.txt @@ -1 +1 @@ -7d11df2f77c8af2da18269532fe276b307dd2a54 +97ccaf599b709902fdf7526db538a25e2460b869 diff --git a/backend/src/monitor.rs b/backend/src/monitor.rs index ea3c99baba..b6be641fcd 100644 --- a/backend/src/monitor.rs +++ b/backend/src/monitor.rs @@ -4229,7 +4229,7 @@ pub async fn monitor_db( // and rotate the ones close to it. Every ~40 min: the values move over days, and // `should_run` counts iterations in a u8. let git_credential_maintenance_f = async { - #[cfg(feature = "private")] + #[cfg(all(feature = "enterprise", feature = "private"))] if server_mode && iteration.is_some() && iteration.as_ref().unwrap().should_run(240) { if let Some(db) = conn.as_sql() { maintain_git_credentials(db).await; @@ -4615,13 +4615,13 @@ const AUTO_PULL_POLL_SLACK_S: i64 = 30; /// Advisory lock id ensuring only one server replica maintains git credentials at /// a time (adjacent to GIT_AUTO_PULL_LOCK_ID). -#[cfg(feature = "private")] +#[cfg(all(feature = "enterprise", feature = "private"))] const GIT_CREDENTIAL_LOCK_ID: i64 = 737_483_923; /// Refresh every git-sync repository's credential status and rotate the ones near /// expiry, so a token dies visibly (and usually not at all) rather than taking /// sync down on its expiry date. -#[cfg(feature = "private")] +#[cfg(all(feature = "enterprise", feature = "private"))] pub async fn maintain_git_credentials(db: &Pool) { use windmill_common::ee_oss::{get_license_plan, LicensePlan}; @@ -4664,7 +4664,7 @@ pub async fn maintain_git_credentials(db: &Pool) { } } -#[cfg(feature = "private")] +#[cfg(all(feature = "enterprise", feature = "private"))] async fn maintain_git_credentials_inner(db: &Pool) -> error::Result<()> { use windmill_common::workspaces::WorkspaceGitSyncSettings; @@ -4718,7 +4718,6 @@ async fn maintain_git_credentials_inner(db: &Pool) -> error::Result<() Ok(()) } -#[cfg(feature = "private")] #[cfg(feature = "private")] async fn poll_git_auto_pull_inner(db: &Pool) -> error::Result<()> { use windmill_common::workspaces::{AutoPullMode, WorkspaceGitSyncSettings}; diff --git a/frontend/src/lib/components/git_sync/GitSyncRepositoryCard.svelte b/frontend/src/lib/components/git_sync/GitSyncRepositoryCard.svelte index ca6a652a33..c2b8d2b7f6 100644 --- a/frontend/src/lib/components/git_sync/GitSyncRepositoryCard.svelte +++ b/frontend/src/lib/components/git_sync/GitSyncRepositoryCard.svelte @@ -187,9 +187,20 @@ } } const days = credentialDaysLeft - if (credential.rotatable || days === undefined || days > 30) return undefined + if (days === undefined) return undefined const when = days <= 0 ? 'has expired' : days === 1 ? 'expires tomorrow' : `expires in ${days} days` + if (credential.rotatable) { + // Renewal starts three weeks out, so a rotatable token this close to expiry + // means it has been failing silently and only a person can find out why. + if (days > 7) return undefined + return { + type: days <= 0 ? ('error' as const) : ('warning' as const), + title: `Repository token ${when}`, + body: 'Windmill renews this token automatically but has not managed to. Check that the instance can reach GitLab, and replace the token if sync has already stopped.' + } + } + if (days > 30) return undefined return { type: days <= 7 ? ('error' as const) : days <= 14 ? ('warning' as const) : ('info' as const), title: `Repository token ${when}`,