mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-10-29 16:15:51 +00:00
fix(datatables): give custom_instance_user the grant option on instance databases
This commit is contained in:
@@ -35,7 +35,10 @@ use windmill_common::runnable_settings::{ConcurrencySettingsWithCustom, Debounci
|
||||
use windmill_common::scripts::ScriptLang;
|
||||
use windmill_common::users::username_to_permissioned_as;
|
||||
use windmill_common::worker::{to_raw_value, SqlAnnotations};
|
||||
use windmill_common::workspaces::get_datatable_resource_from_db_unchecked;
|
||||
use windmill_common::ensure_instance_db_grant_options;
|
||||
use windmill_common::workspaces::{
|
||||
get_datatable_resource_from_db_unchecked, DataTableCatalogResourceType,
|
||||
};
|
||||
use windmill_common::{PgDatabase, DB};
|
||||
use windmill_git_sync::{
|
||||
handle_deployment_metadata, handle_deployment_metadata_batch, DeployedObject,
|
||||
@@ -854,6 +857,25 @@ async fn enable_datatable_migrations(
|
||||
)));
|
||||
}
|
||||
|
||||
// A data table provisioned before instance grants carried `WITH GRANT
|
||||
// OPTION` cannot hand its privileges to the roles the permissions feature
|
||||
// creates. Opting in to migrations is where an admin passes through, so
|
||||
// repair it here; it is a no-op for one that already has them, and only
|
||||
// applies to instance databases, where Windmill owns the Postgres user.
|
||||
if let Ok(datatable) = crate::datatable_permissions::read_datatable(&db, &w_id, &datatable_name).await {
|
||||
if datatable.database.resource_type == DataTableCatalogResourceType::Instance {
|
||||
if let Err(e) =
|
||||
ensure_instance_db_grant_options(&db, &datatable.database.resource_path).await
|
||||
{
|
||||
tracing::warn!(
|
||||
"Could not refresh the grant options of instance database '{}': {}. Continuing.",
|
||||
datatable.database.resource_path,
|
||||
e
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
audit_log(
|
||||
&db,
|
||||
&authed,
|
||||
|
||||
@@ -1397,6 +1397,53 @@ pub async fn drop_custom_instance_database(db: &DB, dbname: &str) -> error::Resu
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// What `custom_instance_user` gets on an instance database.
|
||||
///
|
||||
/// `WITH GRANT OPTION` throughout: data table permissions have Windmill connect
|
||||
/// as this role and hand privileges to the Postgres roles it creates, and a
|
||||
/// privilege it cannot pass on is one those roles can never receive. The
|
||||
/// database and schema `public` are owned by the instance's own Postgres user,
|
||||
/// not by this one, so re-granting is the only way it can reach them.
|
||||
fn instance_db_grants(dbname: &str) -> String {
|
||||
format!(
|
||||
"GRANT CONNECT ON DATABASE \"{dbname}\" TO custom_instance_user WITH GRANT OPTION;
|
||||
GRANT USAGE ON SCHEMA public TO custom_instance_user WITH GRANT OPTION;
|
||||
GRANT CREATE ON SCHEMA public TO custom_instance_user WITH GRANT OPTION;
|
||||
GRANT CREATE ON DATABASE \"{dbname}\" TO custom_instance_user WITH GRANT OPTION;
|
||||
ALTER DEFAULT PRIVILEGES IN SCHEMA public
|
||||
GRANT SELECT, INSERT, UPDATE, DELETE ON TABLES TO custom_instance_user;"
|
||||
)
|
||||
}
|
||||
|
||||
/// Re-apply those grants to an instance database that already exists.
|
||||
///
|
||||
/// One provisioned before the grant options were added holds the privileges
|
||||
/// without them, and only the instance's own Postgres user — the connection
|
||||
/// this runs on — can add them: a role cannot give itself a grant option it
|
||||
/// does not hold.
|
||||
pub async fn ensure_instance_db_grant_options(db: &DB, dbname: &str) -> error::Result<()> {
|
||||
let dbname = dbname.trim();
|
||||
validate_dbname(dbname)?;
|
||||
|
||||
let wmill_pg_creds = PgDatabase::parse_uri(&get_database_url().await?.as_str().await)?;
|
||||
let creds = PgDatabase { dbname: dbname.to_string(), ..wmill_pg_creds };
|
||||
let (client, connection) = creds.connect(Some(db)).await?;
|
||||
let join_handle = tokio::spawn(async move { connection.await });
|
||||
|
||||
let result = client.batch_execute(&instance_db_grants(dbname)).await;
|
||||
|
||||
drop(client);
|
||||
shutdown_pg_connection(join_handle).await?;
|
||||
|
||||
result.map_err(|e| {
|
||||
error::Error::internal_err(format!(
|
||||
"Failed to grant permissions on '{}': {}",
|
||||
dbname,
|
||||
crate::error::pg_error_message(&e)
|
||||
))
|
||||
})
|
||||
}
|
||||
|
||||
/// Create a custom instance database: CREATE DATABASE, grant permissions, register in global_settings.
|
||||
/// The `tag` is stored in global_settings metadata (e.g. "datatable" or "ducklake").
|
||||
pub async fn create_custom_instance_database(
|
||||
@@ -1437,14 +1484,7 @@ pub async fn create_custom_instance_database(
|
||||
let join_handle = tokio::spawn(async move { connection.await });
|
||||
|
||||
if let Err(e) = client
|
||||
.batch_execute(&format!(
|
||||
"GRANT CONNECT ON DATABASE \"{dbname}\" TO custom_instance_user;
|
||||
GRANT USAGE ON SCHEMA public TO custom_instance_user;
|
||||
GRANT CREATE ON SCHEMA public TO custom_instance_user;
|
||||
GRANT CREATE ON DATABASE \"{dbname}\" TO custom_instance_user;
|
||||
ALTER DEFAULT PRIVILEGES IN SCHEMA public
|
||||
GRANT SELECT, INSERT, UPDATE, DELETE ON TABLES TO custom_instance_user;"
|
||||
))
|
||||
.batch_execute(&instance_db_grants(dbname))
|
||||
.await
|
||||
{
|
||||
tracing::warn!(
|
||||
|
||||
Reference in New Issue
Block a user