New to Kestra?
Use blueprints to kickstart your first workflows.
Audit ADLS Gen2 for files/directories granting write access to other via POSIX ACLs, and alert Slack on breach.
ADLS Gen2's hierarchical namespace brings real POSIX-style access control lists to
object storage - the same Read/Write/Execute model as a Linux filesystem, evaluated
against the owning user, owning group, named users and groups, and finally "other":
every remaining security principal in the tenant. A file or directory with write
permission granted to "other" is the cloud-storage equivalent of chmod 777 - and
unlike a container's public-access setting, it is set per-object and easy to lose track
of across a large hierarchy. This blueprint lists a directory, reads each entry's own
accessControlList, and alerts Slack the moment one grants "other" write access.
periodic_acl_check (io.kestra.plugin.core.trigger.Schedule) runs every 6 hours, shipped disabled so you can validate a manual run first.audit_directory_acls (io.kestra.plugin.azure.storage.adls.List) lists directory_path with maxFiles: 1000 (raised from the task's documented default of 25), returning each entry's name, isDirectory, owner, group, permissions, and accessControlList.evaluate_acl_risk (io.kestra.plugin.scripts.python.Script) parses each entry's accessControlList array and flags any entry with an other (or default:other) ACL entry whose permission string contains w, emitting world_writable_count and world_writable_entries through Kestra.outputs.evaluate_acl_gate (io.kestra.plugin.core.flow.If) flags a breach whenever world_writable_count > max_world_writable_entries.then (breach): alert_world_writable_risk pages Slack with the count and location.else (compliant): log_acl_healthy records a clean check.log_audit_complete always runs last, printing the headline numbers regardless of which branch fired.errors block alerts Slack separately if the flow itself fails - an invalid SAS token or unreachable account must not read as "no breach".List output (permissions, owner, group, accessControlList, lease state) - far richer than the basic key/size listing most object-storage plugins in this catalog expose.accessControlList beyond basic file transfer.ADLS Gen2 enforces whatever ACL is set on each object; it does not audit itself for an overly permissive one, and there is no built-in alert for "other" gaining write access. Kestra supplies the schedule, the risk classification as a first-class branch, and an execution history that shows exactly when a world-writable entry first appeared.
directory_path.Local testing: this plugin calls Azure's own hosted Data Lake Storage REST API directly; there is no official self-hosted ADLS Gen2 emulator equivalent to Azurite for Blob Storage (Azurite's Gen2 support is explicitly partial per Microsoft's own documentation). Validate this flow against a real, low-cost test storage account with Hierarchical Namespace enabled rather than attempting a fully local run - this has not been exercised in this environment.
ADLS_SAS_TOKEN: SAS token used by audit_directory_acls, matching the plugin's own documented sasToken property. A shared key (sharedKeyAccountName/sharedKeyAccountAccessKey) is also documented as an alternative; use whichever matches your account's configured auth.SLACK_WEBHOOK_URL: Slack incoming webhook used by alert_world_writable_risk and the errors block.SECRET_, base64-encoded, and read back in flows with {{ secret('NAME') }} - for example SECRET_ADLS_SAS_TOKEN=$(echo -n '<your-sas-token>' | base64). This keeps credentials out of the flow YAML but, per Kestra's own documentation, offers no encryption at rest or access control beyond the host environment; use the Enterprise secrets backend for stronger guarantees.storage_account_endpoint (STRING, default https://example.dfs.core.windows.net): storage account endpoint.file_system (STRING, no default): file system (container) audited.directory_path (STRING, default /): directory audited.max_world_writable_entries (INT, default 0): breach threshold; 0 means any world-writable entry at all breaches.outputs.audit_directory_acls.files: the raw listing (name, isDirectory, owner, group, permissions, accessControlList, and more) for every run.outputs.evaluate_acl_risk.vars.world_writable_count / .world_writable_entries: the computed risk result for every run.storage_account_endpoint/file_system/directory_path at a real ADLS Gen2 test directory containing at least one file or directory with an other::rwx-style ACL entry set deliberately for this test.ADLS_SAS_TOKEN and SLACK_WEBHOOK_URL secrets.alert_world_writable_risk fires naming the expected entry.log_acl_healthy now fires instead.periodic_acl_check once you trust the check.directory_path values with io.kestra.plugin.core.flow.Loop (ForEach is deprecated) to sweep multiple top-level directories, since List is not documented as recursive beyond its own directory level; see Pitfalls.evaluate_acl_risk to also flag entries where a named user or group (not just "other") has been granted unexpectedly broad access, by cross-referencing against an allowlist input.world_writable_entries into a ticketing system task instead of (or alongside) the Slack alert, so each finding becomes a tracked remediation item.auto_remediate/dry_run pattern used by this repo's other sentinels - deliberately not attempted here; see Pitfalls.Delete, DeleteFiles, List, Read, Reads, SharedAccess, Upload - none of them write accessControlList, owner, group, or permissions. This is why this blueprint is audit-only; remediating a finding means using the Azure portal, Storage Explorer, CLI, or SDK outside this flow.type:id:perms versus default:type:id:perms) is confirmed from Azure's own ACL documentation, not from this Kestra plugin's docs, which only confirm accessControlList is "an array" without specifying entry format. evaluate_acl_risk's parsing handles both the 2-segment and default-prefixed 3-segment forms by reading the last three :-delimited parts, but this has not been exercised against a live account in this environment.List's documented maxFiles default is 25, and the plugin's own description does not confirm whether listing recurses into subdirectories. A directory with more entries, or a deeper hierarchy, than this flow's single List call covers will be under-audited; see How to extend.evaluate_acl_risk still correctly evaluates the other entry regardless of what named-identity GUIDs appear elsewhere in the same list.