mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-09-11 00:06:06 +00:00
fix: restrict filesystem workspace storage to debug builds (#10864)
* fix: restrict filesystem workspace storage to debug builds Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Q7p2VbtYqaXHGaAskgwVk5 * chore: update ee-repo-ref to b58ad414b098d3d7787001a352bfbb13e43a335f This commit updates the EE repository reference after PR #747 was merged in windmill-ee-private. Previous ee-repo-ref: 1b4dada77a8fe2224579c643550c63b1ac2616de New ee-repo-ref: b58ad414b098d3d7787001a352bfbb13e43a335f Automated by sync-ee-ref workflow. --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> Co-authored-by: windmill-internal-app[bot] <windmill-internal-app[bot]@users.noreply.github.com>
This commit is contained in:
co-authored by
Claude Opus 5
windmill-internal-app[bot]
parent
f131c3920f
commit
8b80b09f33
+5
-3
@@ -131,9 +131,11 @@ minimal explicit set for dev.
|
||||
## Workspace object storage in dev — use the local filesystem
|
||||
|
||||
For a dev workspace you don't need MinIO/S3: use the built-in **`FilesystemStorage`** large-file
|
||||
storage (a root path on local disk). It is intentionally hidden from the settings-UI storage
|
||||
dropdown (dev-only), so set it via the API. Requires the backend built with `parquet` (+ `private`
|
||||
for the real S3 helpers, + `enterprise` if you want advanced permission rules enforced):
|
||||
storage (a root path on local disk). It is a **debug-build affordance only** — every site that
|
||||
builds a filesystem object store calls `ensure_filesystem_storage_allowed`, so release builds
|
||||
refuse it, and the settings UI never offers it — so set it via the API on a `cargo run`/`cargo
|
||||
test` binary. Requires the backend built with `parquet` (+ `private` for the real S3 helpers,
|
||||
+ `enterprise` if you want advanced permission rules enforced):
|
||||
|
||||
```bash
|
||||
curl -X POST "$BASE/api/w/<ws>/workspaces/edit_large_file_storage_config" \
|
||||
|
||||
@@ -1 +1 @@
|
||||
212cc7d61ec38580d4a70d9ac38d7a2cc9daf409
|
||||
b58ad414b098d3d7787001a352bfbb13e43a335f
|
||||
|
||||
@@ -1970,6 +1970,23 @@ async fn edit_large_file_storage_config(
|
||||
)));
|
||||
}
|
||||
|
||||
if !windmill_common::workspaces::filesystem_storage_allowed() {
|
||||
let named = std::iter::once(("primary storage", &lfs_config.large_file_storage)).chain(
|
||||
lfs_config
|
||||
.secondary_storage
|
||||
.iter()
|
||||
.map(|(name, storage)| (name.as_str(), storage)),
|
||||
);
|
||||
for (name, storage) in named {
|
||||
if matches!(storage, LargeFileStorage::FilesystemStorage(_)) {
|
||||
return Err(Error::BadRequest(format!(
|
||||
"{name}: {}",
|
||||
windmill_common::workspaces::FILESYSTEM_STORAGE_DEV_ONLY_MSG
|
||||
)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let serialized_lfs_config =
|
||||
serde_json::to_value::<LargeFileStorageWithSecondary>(lfs_config)
|
||||
.map_err(|err| Error::internal_err(err.to_string()))?;
|
||||
|
||||
@@ -2193,6 +2193,32 @@ pub fn lfs_entry_storage_ref(entry: &serde_json::Value) -> Option<String> {
|
||||
Some(format!("{typ}:{path}"))
|
||||
}
|
||||
|
||||
pub const FILESYSTEM_STORAGE_DEV_ONLY_MSG: &str =
|
||||
"Filesystem storage is only available in development builds of Windmill: it points the \
|
||||
workspace at a directory on the server's own disk rather than at a resource. Use an S3, \
|
||||
Azure Blob or Google Cloud Storage backend instead.";
|
||||
|
||||
/// A filesystem workspace storage names a directory on the server's own disk, so it hands whoever
|
||||
/// configures it — a workspace admin, or any member who can write a `filesystem` resource —
|
||||
/// whatever the server process can reach, and it only resolves when server and workers share that
|
||||
/// disk. It is there so local development can skip MinIO, hence debug builds only. Instance object
|
||||
/// storage on local disk is a separate, superadmin-only setting and stays allowed everywhere.
|
||||
pub fn filesystem_storage_allowed() -> bool {
|
||||
cfg!(debug_assertions)
|
||||
}
|
||||
|
||||
/// Guards every site that builds an `ObjectStoreResource::Filesystem`, so nothing downstream can
|
||||
/// reach a local-disk store: a stored config outlives the build that accepted it, and the resource
|
||||
/// route never passes through the workspace-storage settings at all.
|
||||
pub fn ensure_filesystem_storage_allowed() -> Result<()> {
|
||||
if !filesystem_storage_allowed() {
|
||||
return Err(Error::BadRequest(
|
||||
FILESYSTEM_STORAGE_DEV_ONLY_MSG.to_string(),
|
||||
));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Resolve a `$res:`/`$var:` reference tree to its concrete value (recursively, secrets
|
||||
/// decrypted). No permission checks — trusted server-side callers only; never echo the result
|
||||
/// to a user.
|
||||
|
||||
@@ -1171,6 +1171,7 @@ pub fn lfs_to_object_store_resource(
|
||||
Ok(ObjectStoreResource::Gcs(gcs_resource))
|
||||
}
|
||||
LargeFileStorage::FilesystemStorage(fs) => {
|
||||
windmill_common::workspaces::ensure_filesystem_storage_allowed()?;
|
||||
Ok(ObjectStoreResource::Filesystem(FilesystemSettings {
|
||||
root_path: fs.root_path.clone(),
|
||||
}))
|
||||
|
||||
@@ -1640,6 +1640,7 @@ pub(crate) async fn get_workspace_s3_resource_path(
|
||||
)
|
||||
}
|
||||
Some(LargeFileStorage::FilesystemStorage(fs)) => {
|
||||
windmill_common::workspaces::ensure_filesystem_storage_allowed()?;
|
||||
return Ok(Some(
|
||||
windmill_object_store::ObjectStoreResource::Filesystem(
|
||||
windmill_object_store::FilesystemSettings { root_path: fs.root_path.clone() },
|
||||
|
||||
@@ -44,6 +44,14 @@
|
||||
onDiscard?: () => void
|
||||
} = $props()
|
||||
|
||||
const creatableStorageTypes = [
|
||||
{ value: 's3', label: 'S3' },
|
||||
{ value: 'azure_blob', label: 'Azure Blob' },
|
||||
{ value: 's3_aws_oidc', label: 'AWS OIDC' },
|
||||
{ value: 'azure_workload_identity', label: 'Azure Workload Identity' },
|
||||
{ value: 'gcloud_storage', label: 'Google Cloud Storage' }
|
||||
]
|
||||
|
||||
let advancedPermissionModalState:
|
||||
| { open: false }
|
||||
| { open: true; storage: S3ResourceSettingsItem } = $state({ open: false })
|
||||
@@ -294,31 +302,38 @@
|
||||
<Cell>
|
||||
<div class="flex gap-2">
|
||||
<div class="relative">
|
||||
{#if tableRow[1].resourceType === 'filesystem'}
|
||||
<!-- Filesystem storage is deliberately absent from the creatable
|
||||
types below: it is dev-only (set via the API), so the UI only
|
||||
renders it read-only when already configured. -->
|
||||
<div class="flex items-center gap-1">
|
||||
<!-- `filesystem` is offered only to a row that already is one, so it can be
|
||||
converted away but never chosen: the backend accepts it in development
|
||||
builds alone. -->
|
||||
<Select
|
||||
items={[{ value: 'filesystem', label: 'Filesystem' }]}
|
||||
value={'filesystem'}
|
||||
disabled
|
||||
items={tableRow[1].resourceType === 'filesystem'
|
||||
? [{ value: 'filesystem', label: 'Filesystem' }, ...creatableStorageTypes]
|
||||
: creatableStorageTypes}
|
||||
bind:value={
|
||||
() => tableRow[1].resourceType,
|
||||
(resourceType) => {
|
||||
if (
|
||||
tableRow[1].resourceType === 'filesystem' &&
|
||||
resourceType !== 'filesystem'
|
||||
) {
|
||||
// A filesystem row holds a server path, not a resource path.
|
||||
tableRow[1].resourcePath = undefined
|
||||
}
|
||||
tableRow[1].resourceType = resourceType
|
||||
}
|
||||
}
|
||||
id="storage-resource-type-select"
|
||||
class="w-40"
|
||||
/>
|
||||
{:else}
|
||||
<Select
|
||||
items={[
|
||||
{ value: 's3', label: 'S3' },
|
||||
{ value: 'azure_blob', label: 'Azure Blob' },
|
||||
{ value: 's3_aws_oidc', label: 'AWS OIDC' },
|
||||
{ value: 'azure_workload_identity', label: 'Azure Workload Identity' },
|
||||
{ value: 'gcloud_storage', label: 'Google Cloud Storage' }
|
||||
]}
|
||||
bind:value={tableRow[1].resourceType}
|
||||
id="storage-resource-type-select"
|
||||
class="w-40"
|
||||
/>
|
||||
{/if}
|
||||
{#if tableRow[1].resourceType === 'filesystem'}
|
||||
<Tooltip>
|
||||
Filesystem storage points the workspace at a directory on the server's own
|
||||
disk. Only development builds of Windmill accept it — switch this storage to
|
||||
S3, Azure Blob or Google Cloud Storage to configure it here.
|
||||
</Tooltip>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex flex-1">
|
||||
{#if tableRow[1].resourceType === 'filesystem'}
|
||||
|
||||
Reference in New Issue
Block a user