mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-08-21 00:02:23 +00:00
6a5cfbc159
* feat: add Entra ID (Azure Workload Identity) support for database auth Add support for Azure Workload Identity to authenticate to Azure Database for PostgreSQL using short-lived Entra ID tokens. Mirrors the existing AWS IAM RDS auth pattern. - Extract shared DatabaseParams to db_params.rs for reuse across providers - Add DatabaseUrl::EntraId variant with token refresh - Detect "entraid" magic password in DATABASE_URL - Unified background refresh task for both IAM RDS and Entra ID - Support sovereign clouds via AZURE_AUTHORITY_HOST env var Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: restore needs_refresh() check in background token refresh task The unified refresh task was missing the needs_refresh() gate, causing it to refresh tokens every 10 seconds instead of only when near expiry. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * chore: update ee-repo-ref.txt for Entra ID branch Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: move entraid env var reads inside cfg(private) block Fixes unused variable warnings in OSS and EE-without-private builds where -D warnings is enabled. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * chore: update ee-repo-ref to 0e001bab643e449b3310b0692dd3598ee0902ecc This commit updates the EE repository reference after PR #483 was merged in windmill-ee-private. Previous ee-repo-ref: 44199013ed0c96680672e718f35124aa34a5d010 New ee-repo-ref: 0e001bab643e449b3310b0692dd3598ee0902ecc Automated by sync-ee-ref workflow. * refactor: add needs_refresh() and refresh_if_needed() to DatabaseUrl Simplify duplicated refresh logic per Claude review suggestion. Background task and get_database_url() now use shared methods instead of matching on each variant individually. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> Co-authored-by: windmill-internal-app[bot] <windmill-internal-app[bot]@users.noreply.github.com>
174 lines
6.4 KiB
Rust
174 lines
6.4 KiB
Rust
use windmill_common::{
|
|
error::{self, Error},
|
|
get_database_url, DatabaseUrl,
|
|
};
|
|
|
|
pub const DEFAULT_MAX_CONNECTIONS_SERVER: u32 = 50;
|
|
pub const DEFAULT_MAX_CONNECTIONS_WORKER: u32 = 5;
|
|
pub const DEFAULT_MAX_CONNECTIONS_INDEXER: u32 = 5;
|
|
|
|
pub async fn initial_connection() -> Result<sqlx::Pool<sqlx::Postgres>, error::Error> {
|
|
let connect_options = get_database_url().await?.connect_options().await?;
|
|
sqlx::postgres::PgPoolOptions::new()
|
|
.max_connections(2)
|
|
.connect_with(connect_options)
|
|
.await
|
|
.map_err(|err| Error::ConnectingToDatabase(err.to_string()))
|
|
}
|
|
|
|
pub async fn connect_db(
|
|
server_mode: bool,
|
|
indexer_mode: bool,
|
|
worker_mode: bool,
|
|
num_workers: i32,
|
|
#[cfg(feature = "private")] mut killpill_rx: tokio::sync::broadcast::Receiver<()>,
|
|
) -> anyhow::Result<sqlx::Pool<sqlx::Postgres>> {
|
|
use anyhow::Context;
|
|
|
|
let database_url = get_database_url().await?;
|
|
|
|
let max_connections = match std::env::var("DATABASE_CONNECTIONS") {
|
|
Ok(n) => n.parse::<u32>().context("invalid DATABASE_CONNECTIONS")?,
|
|
Err(_) => {
|
|
if server_mode {
|
|
DEFAULT_MAX_CONNECTIONS_SERVER
|
|
} else if indexer_mode {
|
|
DEFAULT_MAX_CONNECTIONS_INDEXER
|
|
} else {
|
|
DEFAULT_MAX_CONNECTIONS_WORKER + (num_workers.max(1) as u32) - 1
|
|
}
|
|
}
|
|
};
|
|
|
|
let pool = connect(database_url.clone(), max_connections, worker_mode).await?;
|
|
|
|
#[cfg(all(feature = "enterprise", feature = "private"))]
|
|
{
|
|
let needs_token_refresh = matches!(
|
|
database_url,
|
|
DatabaseUrl::IamRds(_) | DatabaseUrl::EntraId(_)
|
|
);
|
|
let label = match &database_url {
|
|
DatabaseUrl::IamRds(_) => "IAM RDS",
|
|
DatabaseUrl::EntraId(_) => "Entra ID",
|
|
DatabaseUrl::Static(_) => "",
|
|
};
|
|
if needs_token_refresh {
|
|
let pool2 = pool.clone();
|
|
let database_url2 = database_url.clone();
|
|
tokio::spawn(async move {
|
|
loop {
|
|
tokio::select! {
|
|
_ = killpill_rx.recv() => {
|
|
break;
|
|
}
|
|
_ = tokio::time::sleep(std::time::Duration::from_secs(10)) => {
|
|
if !database_url2.needs_refresh().await {
|
|
continue;
|
|
}
|
|
let new_url = tokio::time::timeout(
|
|
std::time::Duration::from_secs(10),
|
|
get_database_url(),
|
|
)
|
|
.await;
|
|
match new_url {
|
|
Ok(Ok(new_url)) => {
|
|
match new_url.connect_options().await {
|
|
Ok(connect_options) => {
|
|
pool2.set_connect_options(connect_options);
|
|
tracing::info!("Refreshed {label} URL successfully");
|
|
}
|
|
Err(e) => {
|
|
tracing::error!(
|
|
"Error getting {label} connect options, retrying in 10s: {e}"
|
|
);
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
Ok(Err(e)) => {
|
|
tracing::error!(
|
|
"Error refreshing {label} URL, trying again in 10s: {e}"
|
|
);
|
|
continue;
|
|
}
|
|
Err(e) => {
|
|
tracing::error!(
|
|
"Timeout after 10s refreshing {label} URL, trying again in 10s: {e}"
|
|
);
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
Ok(pool)
|
|
}
|
|
|
|
pub async fn connect(
|
|
database_url: DatabaseUrl,
|
|
max_connections: u32,
|
|
worker_mode: bool,
|
|
) -> Result<sqlx::Pool<sqlx::Postgres>, error::Error> {
|
|
use sqlx::Executor;
|
|
use std::time::Duration;
|
|
let mut pool_options = sqlx::postgres::PgPoolOptions::new()
|
|
.min_connections(0)
|
|
.max_connections(max_connections)
|
|
.max_lifetime(Duration::from_secs(30 * 60)); // 30 mins
|
|
if worker_mode {
|
|
pool_options = pool_options.idle_timeout(Duration::from_secs(60));
|
|
}
|
|
pool_options
|
|
.after_connect(move |conn, _| {
|
|
if worker_mode {
|
|
Box::pin(async move {
|
|
if let Err(e) = conn
|
|
.execute(
|
|
r#"
|
|
SET enable_seqscan = OFF;
|
|
SET statement_timeout = '5min';
|
|
SET idle_in_transaction_session_timeout = '10min';
|
|
SET tcp_keepalives_idle = 300;
|
|
SET tcp_keepalives_interval = 60;
|
|
SET tcp_keepalives_count = 10;"#,
|
|
)
|
|
.await
|
|
{
|
|
tracing::error!("Error setting postgres settings: {}", e);
|
|
}
|
|
Ok(())
|
|
})
|
|
} else {
|
|
Box::pin(async move {
|
|
if let Err(e) = conn
|
|
.execute(
|
|
r#"
|
|
SET statement_timeout = '5min';
|
|
SET idle_in_transaction_session_timeout = '10min';
|
|
SET tcp_keepalives_idle = 300;
|
|
SET tcp_keepalives_interval = 60;
|
|
SET tcp_keepalives_count = 10;"#,
|
|
)
|
|
.await
|
|
{
|
|
tracing::error!("Error setting postgres settings: {}", e);
|
|
}
|
|
Ok(())
|
|
})
|
|
}
|
|
})
|
|
.connect_with(
|
|
database_url
|
|
.connect_options()
|
|
.await?
|
|
.statement_cache_capacity(400),
|
|
)
|
|
.await
|
|
.map_err(|err| Error::ConnectingToDatabase(err.to_string()))
|
|
}
|