Schedule icon
List icon
Script icon
If icon
SlackIncomingWebhook icon
Log icon

ADLS World-Writable ACL Sentinel

Audit ADLS Gen2 for files/directories granting write access to other via POSIX ACLs, and alert Slack on breach.

Categories
CloudInfrastructure

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.

How it works

  1. periodic_acl_check (io.kestra.plugin.core.trigger.Schedule) runs every 6 hours, shipped disabled so you can validate a manual run first.
  2. 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.
  3. 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.
  4. 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.
  5. log_audit_complete always runs last, printing the headline numbers regardless of which branch fired.
  6. The errors block alerts Slack separately if the flow itself fails - an invalid SAS token or unreachable account must not read as "no breach".

What you get

  • A concrete, named list of files and directories granting write access to every security principal in the tenant, computed from ADLS Gen2's own POSIX ACL model, not a guess from container-level settings.
  • A final status log every run, breach or not, so the execution history doubles as an ACL-hygiene trend for the directory.
  • A worked example of this plugin's unusually rich List output (permissions, owner, group, accessControlList, lease state) - far richer than the basic key/size listing most object-storage plugins in this catalog expose.

Who it's for

  • Platform and security teams running ADLS Gen2 with Hierarchical Namespace enabled who need a standing check against POSIX ACL misconfiguration, not a one-time Storage Explorer review.
  • Data engineering teams managing shared data-lake directories (ingestion zones, curated layers) where multiple teams set ACLs independently and drift is easy to introduce.
  • Anyone evaluating this plugin who wants a worked example of auditing accessControlList beyond basic file transfer.

Why orchestrate this with Kestra

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.

Prerequisites

  • An ADLS Gen2 storage account (Hierarchical Namespace enabled) and a SAS token (or shared key) with rights to list directory_path.
  • A Slack incoming webhook for alerts.

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.

Secrets

  • 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.
  • In Kestra OSS (no Enterprise secrets backend), secrets are supplied as environment variables prefixed 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.

Inputs

  • 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

  • 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.

Quick start

  1. Point 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.
  2. Add the ADLS_SAS_TOKEN and SLACK_WEBHOOK_URL secrets.
  3. Run the flow manually and confirm alert_world_writable_risk fires naming the expected entry.
  4. Remove the write bit from that entry's "other" permission (through the Azure portal, Storage Explorer, or CLI, per Microsoft's own ACL management guides) and re-run; confirm log_acl_healthy now fires instead.
  5. Enable periodic_acl_check once you trust the check.

How to extend

  • Loop over several 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.
  • Extend 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.
  • If you maintain a change-management process for ACLs, pipe world_writable_entries into a ticketing system task instead of (or alongside) the Slack alert, so each finding becomes a tracked remediation item.
  • Should this plugin later add a structured ACL-management task, extend this blueprint with the same double-gated auto_remediate/dry_run pattern used by this repo's other sentinels - deliberately not attempted here; see Pitfalls.

Pitfalls

  • This plugin has no task to set or modify an ACL. Verified task list: 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.
  • The exact per-entry ACL string format (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.
  • ACLs only matter if Hierarchical Namespace (HNS) is enabled on the storage account. Per Microsoft's own documentation, if HNS is off, Azure RBAC alone governs access and the POSIX ACL model (and this entire audit) does not apply.
  • A GUID in place of a readable name in an ACL entry is not itself a bug. Per Microsoft's own FAQ, this happens when the referenced Microsoft Entra identity was deleted; evaluate_acl_risk still correctly evaluates the other entry regardless of what named-identity GUIDs appear elsewhere in the same list.

Links

See How

New to Kestra?

Use blueprints to kickstart your first workflows.