diff --git a/backend/ee-repo-ref.txt b/backend/ee-repo-ref.txt index c64abf81cd..45b6cdc229 100644 --- a/backend/ee-repo-ref.txt +++ b/backend/ee-repo-ref.txt @@ -1 +1 @@ -017d36418a65ce5c840c502e3174df0c393612ba +0189ba6504fd70eb4929e4881d624d48efd14aee diff --git a/backend/windmill-api/openapi.yaml b/backend/windmill-api/openapi.yaml index 00fd942598..4b3da914f7 100644 --- a/backend/windmill-api/openapi.yaml +++ b/backend/windmill-api/openapi.yaml @@ -21076,6 +21076,9 @@ components: mount_path: type: string description: KV v2 secrets engine mount path (e.g., windmill) + kv_secret_path_prefix: + type: string + description: Optional path prefix inserted between the KV data/metadata segment and the workspace id (e.g., "apps/windmill"). When set, secrets are stored at `/data///`, allowing a Vault policy scoped to exactly `/data//*`. jwt_role: type: string description: Vault JWT auth role name for Windmill (optional, if not provided token auth is used) diff --git a/backend/windmill-common/src/secret_backend/mod.rs b/backend/windmill-common/src/secret_backend/mod.rs index 36f35f0cf8..71fa2ea999 100644 --- a/backend/windmill-common/src/secret_backend/mod.rs +++ b/backend/windmill-common/src/secret_backend/mod.rs @@ -118,6 +118,13 @@ pub struct VaultSettings { pub address: String, /// KV v2 mount path (e.g., "windmill") pub mount_path: String, + /// Optional path prefix inserted between the KV `data`/`metadata` segment + /// and the workspace id, e.g. "apps/windmill". When set, secrets live at + /// `/data///`, so a Vault policy can be + /// scoped to exactly `/data//*`. Surrounding slashes are + /// trimmed. + #[serde(skip_serializing_if = "Option::is_none")] + pub kv_secret_path_prefix: Option, /// JWT auth role name configured in Vault (used for JWT/OIDC auth) /// Optional - if not provided, token auth is used #[serde(skip_serializing_if = "Option::is_none")] diff --git a/backend/windmill-common/src/secret_backend/tests.rs b/backend/windmill-common/src/secret_backend/tests.rs index 3e2a12382a..8f04a755b7 100644 --- a/backend/windmill-common/src/secret_backend/tests.rs +++ b/backend/windmill-common/src/secret_backend/tests.rs @@ -25,6 +25,7 @@ mod tests { VaultSettings { address: "http://127.0.0.1:8200".to_string(), mount_path: "windmill".to_string(), + kv_secret_path_prefix: None, jwt_role: Some("windmill-secrets".to_string()), jwt_mount_path: None, namespace: None, diff --git a/backend/windmill-common/tests/secret_backend_integration.rs b/backend/windmill-common/tests/secret_backend_integration.rs index 350fc297af..ff41f3acf7 100644 --- a/backend/windmill-common/tests/secret_backend_integration.rs +++ b/backend/windmill-common/tests/secret_backend_integration.rs @@ -90,6 +90,7 @@ mod tests { address: std::env::var("VAULT_ADDR") .unwrap_or_else(|_| "http://127.0.0.1:8200".to_string()), mount_path: "windmill".to_string(), + kv_secret_path_prefix: None, jwt_role: None, // Static token mode jwt_mount_path: None, namespace: None, @@ -106,6 +107,7 @@ mod tests { address: std::env::var("VAULT_ADDR") .unwrap_or_else(|_| "http://127.0.0.1:8200".to_string()), mount_path: "windmill".to_string(), + kv_secret_path_prefix: None, jwt_role: Some("windmill-secrets".to_string()), // JWT mode jwt_mount_path: None, namespace: None, diff --git a/backend/windmill-common/tests/secret_backend_migration.rs b/backend/windmill-common/tests/secret_backend_migration.rs index fba27ee260..5a4086fa1b 100644 --- a/backend/windmill-common/tests/secret_backend_migration.rs +++ b/backend/windmill-common/tests/secret_backend_migration.rs @@ -34,6 +34,7 @@ fn test_vault_settings() -> VaultSettings { address: std::env::var("VAULT_ADDR") .unwrap_or_else(|_| "http://127.0.0.1:8200".to_string()), mount_path: "windmill".to_string(), + kv_secret_path_prefix: None, jwt_role: Some("windmill-secrets".to_string()), jwt_mount_path: None, namespace: None, diff --git a/frontend/src/lib/components/instanceSettings/SecretBackendConfig.svelte b/frontend/src/lib/components/instanceSettings/SecretBackendConfig.svelte index b14dec9846..7ecec72dfd 100644 --- a/frontend/src/lib/components/instanceSettings/SecretBackendConfig.svelte +++ b/frontend/src/lib/components/instanceSettings/SecretBackendConfig.svelte @@ -69,6 +69,7 @@ type: 'HashiCorpVault', address: $values['secret_backend']?.address ?? '', mount_path: $values['secret_backend']?.mount_path ?? 'windmill', + kv_secret_path_prefix: $values['secret_backend']?.kv_secret_path_prefix ?? null, jwt_role: $values['secret_backend']?.jwt_role ?? 'windmill-secrets', jwt_mount_path: $values['secret_backend']?.jwt_mount_path ?? null, namespace: $values['secret_backend']?.namespace ?? null, @@ -122,6 +123,7 @@ return { address: $values['secret_backend'].address, mount_path: $values['secret_backend'].mount_path, + kv_secret_path_prefix: $values['secret_backend'].kv_secret_path_prefix || undefined, jwt_role: $values['secret_backend'].jwt_role, jwt_mount_path: $values['secret_backend'].jwt_mount_path || undefined, namespace: $values['secret_backend'].namespace || undefined, @@ -355,6 +357,11 @@ let baseUrl = $derived($values['base_url'] ?? 'https://your-windmill-instance.com') let jwtMount = $derived(($values['secret_backend']?.jwt_mount_path?.trim() || 'jwt') as string) + let kvPrefix = $derived( + ($values['secret_backend']?.kv_secret_path_prefix?.trim().replace(/^\/+|\/+$/g, '') || + '') as string + ) + let kvPolicyPath = $derived(kvPrefix ? `${kvPrefix}/*` : '*') let vaultAudience = $derived( ($values['secret_backend']?.address?.trim() || 'https://vault.example.com:8200') as string ) @@ -453,6 +460,27 @@ bind:value={$values['secret_backend'].mount_path} /> +
+ + Optional prefix inserted before the workspace id. When set, secrets are stored at + <mount>/data/<prefix>/<workspace>/<secret>, so you + can keep an existing layout and scope a Vault policy to exactly + {$values['secret_backend']?.mount_path ?? 'windmill'}/data/{kvPolicyPath}. + +
Authentication Method setAuthMethod(v)}> @@ -541,10 +569,10 @@ vault write auth/{jwtMount}/config \ # Create a policy for Windmill secrets vault policy write windmill-secrets - <<EOF -path "{$values['secret_backend']?.mount_path ?? 'windmill'}/data/*" { +path "{$values['secret_backend']?.mount_path ?? 'windmill'}/data/{kvPolicyPath}" { capabilities = ["create", "read", "update", "delete"] } -path "{$values['secret_backend']?.mount_path ?? 'windmill'}/metadata/*" { +path "{$values['secret_backend']?.mount_path ?? 'windmill'}/metadata/{kvPolicyPath}" { capabilities = ["list", "delete"] } EOF