New to Kestra?
Use blueprints to kickstart your first workflows.
Find MinIO buckets exposed by an anonymous download, upload or public policy, then run mc anonymous set none behind an approval and re-verify.
A MinIO bucket left with an anonymous download, upload or public policy serves its objects to anyone on the internet with no credentials. It is the classic public bucket data exposure. It usually comes from a quick "just make it work" during setup that nobody walks back. This blueprint finds those buckets, records exactly what each one is handing out, then removes the anonymous policy behind an approval and proves the bucket is private again.
This blueprint was created by zkasuran.
Locking a bucket down removes anonymous access, which breaks any anonymous client currently reading from it. That is the point of the fix, not a side effect, so the destructive step sits behind a Pause: a person approves the exact set of buckets before any policy changes. Read-only is the default (dry_run: true, auto_remediate: false), the allowlist spares buckets that are public on purpose. A ceiling refuses a run that would change more buckets than you expect.
scan_policies (io.kestra.plugin.scripts.shell.Commands on the mc image) lists every bucket and reads each one's anonymous policy with mc anonymous get. A policy other than private means the bucket is exposed. Exposed buckets that are not on the allowlist are flagged. A connection or credential failure fails the task, so an unreachable endpoint never reads as a clean result.inventory_exposed (io.kestra.plugin.core.flow.Loop) runs list_objects (io.kestra.plugin.minio.List) on each flagged bucket to record the objects an unauthenticated caller can read right now. This is the blast radius, captured before anything changes.gate (io.kestra.plugin.core.flow.If) changes a policy only when auto_remediate is true, dry_run is false and at least one bucket is flagged. ceiling_check refuses a run that would exceed max_lockdowns.confirm (io.kestra.plugin.core.flow.Pause) holds for a human. On approval, remediate_buckets (io.kestra.plugin.core.flow.Loop, concurrencyLimit: 1) runs set_none (mc anonymous set none) on each flagged bucket, then reverify_bucket reads the policy again and assert_locked fails the run if the bucket is still exposed.report posts a summary to Slack. The errors block posts a separate alert if the scan fails, a lockdown errors or a bucket is still exposed after remediation.private as exposed, so an unusual or custom grant is caught rather than skipped.mc anonymous set none only after a person approves the exact buckets.Finding a public bucket is one plugin call. Fixing it is another. Proving the fix landed is a third. Kestra ties them into one audited run: the native MinIO plugin inventories the exposure, the mc client reads and sets the policy, the Pause puts a person between detection and a change that breaks live clients. The re-verify step makes the whole thing fail loudly rather than quietly leaving a bucket open. The policy lives in version-controlled YAML, not in a one-off script somebody ran once.
Run against a MinIO server with four buckets: one anonymous download, one anonymous upload, one private and one download bucket on the allowlist. The scan flags the two exposed non-allowlisted buckets and spares the allowlisted one. A dry run reports them without changing anything. With auto_remediate: true, dry_run: false and an approval, both are set to private, an unauthenticated read that worked before the run is refused after it. The private bucket is never touched. A re-verify against a bucket a concurrent writer keeps re-opening fails the run as designed. An unreachable endpoint routes to the Slack alert.
MINIO_ACCESS_KEY_ID: MinIO access key with rights to read and set bucket policies.MINIO_SECRET_KEY_ID: MinIO secret key.SLACK_WEBHOOK_URL: Slack incoming webhook for the summary and the failure alert.dry_run (BOOL, default true): report only when true.auto_remediate (BOOL, default false): lock exposed buckets down when true and dry_run is false.minio_endpoint (STRING): the MinIO S3 API endpoint URL.minio_access_key (STRING, default secret('MINIO_ACCESS_KEY_ID')), minio_secret_key (STRING, default secret('MINIO_SECRET_KEY_ID')).allowlist (ARRAY of STRING, default empty): buckets that are public on purpose and are never changed.max_lockdowns (INT, default 25): ceiling on how many buckets one run may change.mc_image (STRING, default minio/mc:latest): the mc client image.docker_network (STRING, default bridge): the Docker network the mc client joins to reach the endpoint. Set it to your MinIO container's network when the endpoint is a container name.minio_endpoint.allowlist and run again. It is reported as spared.auto_remediate: true and dry_run: false. Approve the pause. The flagged buckets are set to private and the run fails if any is still open.disabled: false on the daily trigger once you are happy with a report-only schedule.allowlist at a flow input or a namespace KV so the public-by-design set is managed outside the flow.mc ls output, for a very large MinIO.mc anonymous set none with a custom policy via mc anonymous set-json to grant scoped access instead of removing it.io.kestra.plugin.minio.List to flag world-readable objects inside an otherwise private bucket.