mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-10-16 00:03:02 +00:00
feat: restricted job tokens (job_token_scopes on scripts and flows)
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
f74113c75a
commit
06a5d73bb8
@@ -897,6 +897,7 @@ async fn is_noop_deploy_against_parent(
|
||||
ns: &NewScript,
|
||||
parent: &Script<ScriptRunnableSettingsHandle>,
|
||||
resolved_on_behalf_of: Option<&str>,
|
||||
resolved_job_token_scopes: Option<&[String]>,
|
||||
db: &DB,
|
||||
) -> Result<bool> {
|
||||
if parent.archived || parent.deleted {
|
||||
@@ -950,6 +951,8 @@ async fn is_noop_deploy_against_parent(
|
||||
// caller-intent flag (auto-resolve parent), not script state
|
||||
auto_parent: _,
|
||||
labels,
|
||||
// resolved against the deployed value into `resolved_job_token_scopes`, compared below
|
||||
job_token_scopes: _,
|
||||
// caller-intent flag (preserve user drafts on CLI/git-sync deploys);
|
||||
// transient, never persisted, does not change what the script *is*
|
||||
skip_draft_deletion: _,
|
||||
@@ -1023,6 +1026,9 @@ async fn is_noop_deploy_against_parent(
|
||||
if resolved_on_behalf_of != parent.on_behalf_of.as_deref() {
|
||||
return Ok(false);
|
||||
}
|
||||
if resolved_job_token_scopes != parent.job_token_scopes.as_deref() {
|
||||
return Ok(false);
|
||||
}
|
||||
// Both of a dbt script's derived fields are compared as they WOULD BE STORED,
|
||||
// not as they arrived: the schema comes from the descriptor and the clients
|
||||
// cannot derive it (`windmill-parser-wasm` has no dbt arm), so they send the
|
||||
@@ -1373,6 +1379,21 @@ async fn create_script_internal<'c>(
|
||||
&db,
|
||||
)
|
||||
.await?;
|
||||
// Absent keeps what the path already has: a client unaware of the setting must not drop a
|
||||
// restriction by redeploying.
|
||||
let resolved_job_token_scopes = match &ns.job_token_scopes {
|
||||
Some(Some(scopes)) => Some(windmill_common::scopes::validate_job_token_scopes(scopes)?),
|
||||
Some(None) => None,
|
||||
None => sqlx::query_scalar!(
|
||||
"SELECT job_token_scopes FROM script WHERE path = $1 AND workspace_id = $2 \
|
||||
AND deleted = false ORDER BY created_at DESC LIMIT 1",
|
||||
&ns.path,
|
||||
&w_id
|
||||
)
|
||||
.fetch_optional(&db)
|
||||
.await?
|
||||
.flatten(),
|
||||
};
|
||||
// Written beside the principal only while a worker that still reads it may be live.
|
||||
let legacy_on_behalf_of_email =
|
||||
windmill_common::legacy_on_behalf_of_email(resolved_on_behalf_of.as_deref(), &w_id, &db)
|
||||
@@ -1618,8 +1639,14 @@ async fn create_script_internal<'c>(
|
||||
// CLI pushes must not produce phantom commits on the downstream
|
||||
// git repository.
|
||||
if skip_if_noop
|
||||
&& is_noop_deploy_against_parent(&ns, &ps, resolved_on_behalf_of.as_deref(), &db)
|
||||
.await?
|
||||
&& is_noop_deploy_against_parent(
|
||||
&ns,
|
||||
&ps,
|
||||
resolved_on_behalf_of.as_deref(),
|
||||
resolved_job_token_scopes.as_deref(),
|
||||
&db,
|
||||
)
|
||||
.await?
|
||||
{
|
||||
tracing::info!(
|
||||
workspace_id = %w_id,
|
||||
@@ -2195,8 +2222,8 @@ async fn create_script_internal<'c>(
|
||||
content, created_by, schema, is_template, extra_perms, lock, language, kind, tag, \
|
||||
envs, concurrent_limit, concurrency_time_window_s, cache_ttl, \
|
||||
dedicated_worker, ws_error_handler_muted, priority, restart_unless_cancelled, \
|
||||
delete_after_use, delete_after_secs, timeout, concurrency_key, visible_to_runner_only, auto_kind, codebase, has_preprocessor, schema_validation, assets, debounce_key, debounce_delay_s, cache_ignore_s3_path, runnable_settings_handle, modules, labels, on_behalf_of, on_behalf_of_email) \
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9::text::json, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21, $22, $23, $24, $25, $26, $27, $28, $29, $30, $31, $32, $33, $34, $35, $36, $37, $38, $39, $40, $41)",
|
||||
delete_after_use, delete_after_secs, timeout, concurrency_key, visible_to_runner_only, auto_kind, codebase, has_preprocessor, schema_validation, assets, debounce_key, debounce_delay_s, cache_ignore_s3_path, runnable_settings_handle, modules, labels, on_behalf_of, on_behalf_of_email, job_token_scopes) \
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9::text::json, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21, $22, $23, $24, $25, $26, $27, $28, $29, $30, $31, $32, $33, $34, $35, $36, $37, $38, $39, $40, $41, $42)",
|
||||
&w_id,
|
||||
&hash.0,
|
||||
ns.path,
|
||||
@@ -2240,9 +2267,14 @@ async fn create_script_internal<'c>(
|
||||
ns.labels.as_deref() as Option<&[String]>,
|
||||
resolved_on_behalf_of,
|
||||
legacy_on_behalf_of_email,
|
||||
resolved_job_token_scopes.as_deref() as Option<&[String]>,
|
||||
)
|
||||
.execute(&mut *tx)
|
||||
.await?;
|
||||
windmill_common::scopes::log_job_token_scopes_deploy(
|
||||
"script",
|
||||
resolved_job_token_scopes.as_deref(),
|
||||
);
|
||||
|
||||
// A lock that is not left to a dependency job queues none, so this is the only place its hash
|
||||
// can be recorded. `try_skip_relock` treats a missing hash for an imported script as changed,
|
||||
@@ -2952,6 +2984,7 @@ async fn create_script_internal<'c>(
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
)
|
||||
.await?;
|
||||
|
||||
@@ -3075,6 +3108,7 @@ async fn create_script_internal<'c>(
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
)
|
||||
.await?;
|
||||
tracing::info!("pushed auto-build binary job {job_id} for {script_path}");
|
||||
|
||||
Reference in New Issue
Block a user