Schedule icon
Webhook icon
WorkingDirectory icon
Commands icon
Docker icon
Script icon
If icon
Create icon
SlackIncomingWebhook icon
Loop icon
Log icon

Audit NetworkPolicy Coverage Across Every Namespace

Audit Kubernetes NetworkPolicy coverage, flag namespaces without a default deny policy and allow-all 0.0.0.0/0 rules, then escalate with GitHub and Slack.

Categories
Infrastructure

How it works

  1. weekly_coverage_audit (io.kestra.plugin.core.trigger.Schedule, cron 0 6 * * 1) is the primary trigger and ships disabled: true, so nothing runs until an operator switches it on. on_demand_audit (io.kestra.plugin.core.trigger.Webhook, keyed) accepts an on-demand audit from a CI job or a laptop.
  2. policy_workspace (io.kestra.plugin.core.flow.WorkingDirectory) opens one shared filesystem so the inventory, the audit, and the remediation yaml all see the same files.
  3. collect_inventory (io.kestra.plugin.scripts.shell.Commands on the io.kestra.plugin.scripts.runner.docker.Docker runner, image bitnami/kubectl:latest) writes namespaces.json, four labeled namespaces, and networkpolicies.json, two default deny policies plus one allow-all ingress policy on 0.0.0.0/0 plus one namespace with no policy at all, with heredocs. It then runs kubectl get networkpolicy -A and captures the JSON listings with || true so an unreachable cluster never fails the run. When both live files come back non-empty they supersede the fixtures, otherwise they are removed and the bundled fixtures keep the audit deterministic offline.
  4. audit_policies (io.kestra.plugin.scripts.python.Script on python:3.12-slim) reads the inventory, marks every namespace with no default deny policy, flags every policy carrying a 0.0.0.0/0 or ::/0 rule, and emits verdict, uncovered_ns, permissive, and namespaces through the outputs protocol.
  5. check_gaps (io.kestra.plugin.core.flow.If) branches on the two lists. When either is non-empty, open_coverage_issue (io.kestra.plugin.github.issues.Create) files a labeled issue, alert_slack posts the counts, and append_remediation (io.kestra.plugin.core.flow.Loop, concurrencyLimit: 1) walks the uncovered namespaces so append_namespace_yaml appends a ready to apply default deny manifest per namespace, plus a tightened replacement for any allow-all policy in that namespace, into remediation.yaml.
  6. When both lists are empty, log_locked_down (io.kestra.plugin.core.log.Log) records the all-clear, so the execution history doubles as audit evidence. The errors block alerts Slack whenever the inventory or the audit breaks, because a dead collector must never read as a locked down cluster.

What you get

  • Gateable results: verdict, uncovered_ns, permissive, and namespaces are flow outputs any downstream flow can branch on.
  • Escalation where engineers already work: a labeled GitHub issue naming every uncovered namespace and every allow-all policy, plus a Slack ping with the counts.
  • remediation.yaml: one default deny NetworkPolicy manifest per uncovered namespace, with a tightened replacement for each allow-all policy, ready to kubectl apply -f.

Who it's for

  • Platform and SRE teams that must prove every namespace ships a default deny baseline.
  • Security engineers chasing allow-all NetworkPolicies that quietly expose workloads to the internet.

Why orchestrate this with Kestra

A kubectl get networkpolicy -A is a one-shot command with no memory, no threshold, and no notification path, and nobody runs it twice. Kestra supplies the disabled-by-default weekly schedule as the safety net, a keyed webhook for on-demand runs, a hermetic kubectl container so the tool version lives in the flow file, a branch that separates escalation from the all-clear, execution history with the inventory attached, and routing into GitHub and Slack from one declarative definition. A cron entry on a bastion host gives you none of that, it only reports whatever the last person remembered to check.

Prerequisites

  • A worker with Docker access, the inventory runs in bitnami/kubectl:latest and the audit in python:3.12-slim.
  • A reachable Kubernetes API for live inventory. Without one the flow still completes on the bundled fixtures, which is exactly what makes it safe to run in CI or on a laptop.
  • A GitHub token that can open issues in github_repository (classic PAT scope repo, or fine-grained Issues: write) and a Slack incoming webhook for escalation and failure alerts.

Secrets

  • NETWORKPOLICY_WEBHOOK_KEY: key segment guarding the on-demand webhook URL, anyone holding it can trigger an audit.
  • GITHUB_TOKEN: token used to open the coverage issue.
  • SLACK_WEBHOOK_URL: Slack incoming webhook URL.

Quick start

  1. Add the three secrets to your Kestra namespace.
  2. Run the flow once with the default inputs and confirm verdict is GAPS_FOUND, uncovered_ns lists analytics and sandbox, permissive lists analytics/allow-all-ingress, and namespaces is 4.
  3. Open remediation.yaml in the working directory of that execution and apply it to a test cluster to watch the verdict return LOCKED_DOWN.
  4. Point github_repository at a repository where issues are welcome, re-run, and confirm the issue and the Slack message arrive, then trigger on_demand_audit at /api/v1/{tenant}/executions/webhook/{namespace}/networkpolicy-coverage-audit/{NETWORKPOLICY_WEBHOOK_KEY} or enable weekly_coverage_audit once a cluster is reachable from the worker.

How to extend

  • Audit a fleet by fanning the cluster_name input out with a Loop over a cluster list, one execution per cluster.
  • Add a core.flow.Fail task under check_gaps to turn a coverage gap into a red run for a CD pipeline.
  • Extend audit_policies with egress-only baselines, or with exemptions for system namespaces such as kube-system, then ship remediation.yaml to S3 as audit evidence.

Links

See How

New to Kestra?

Use blueprints to kickstart your first workflows.