PipAuditWatch - Python dependency CVE watcher
Scans your Python requirements for known vulnerabilities and alerts on Slack.
Compare phpIPAM subnet VLAN plus VRF assignments against a declared design, correct the drift behind an approval gate, then re-read to prove it.
Two things about this plugin shape the flow, both measured on a real phpIPAM instance.
io.kestra.plugin.phpipam.ipam.subnet.Update carries no output at all. It takes subnetId,
vlanId, vrfId plus resourceDescription, returns nothing, so the task going green says
only that phpIPAM answered. That is why the flow reads the section a second time with
ipam.subnet.List then recomputes the comparison, rather than trusting the write.
ipam.subnet.List returns vlanId plus vrfId as JSON numbers while an unassigned subnet
carries 0. The comparison normalises null, empty string plus 0 to one value, so a subnet
that is deliberately unassigned does not read as drift.
One failure mode to know about. phpIPAM refuses a vlanId that is not in its VLAN table with
HTTP 404 Vlan does not exist, which the plugin reports as Resource not found naming the
subnet path. The subnet is fine, the declared VLAN is the thing that does not exist, so check
the design against GET /api/{appId}/vlan/ when a correction fails that way. A write the
application has no permission for comes back as HTTP 401 instead, which fails the task.
Rewriting a VLAN or VRF assignment in IPAM moves nothing on the network. It changes what the next firewall rule, routing policy or template generated from IPAM will target. So a wrong baseline propagates a wrong segment with full confidence. The flow is built around that:
protected_subnet_ids subnets are reported when they drift, never writtenmax_corrections_per_run refuses the whole correctionauto_remediate true, dry_run false plus a Pause answered trueparse_baseline counts the usable entries in declared_baseline. An unparseable design
fails this task under allowFailure, which is what the refusal reads.refuse_unusable_baseline stops the run when that count is zero or absent. Nothing has
touched phpIPAM yet at this point.read_assignments_before lists every subnet in the section with its live vlanId plus
vrfId.detect_drift joins the design to that read, one row per disagreeing subnet, each
classified WRITABLE, PROTECTED or MISSING with both the old plus the new value.log_drift_report writes the counts, the protected skips plus the full detail to the log.refuse_over_ceiling fails when more subnets are WRITABLE than the per-run ceiling allows.remediation_gate opens only for auto_remediate true with dry_run false. It pauses for
approval, then a serialised Loop calls ipam.subnet.Update once per WRITABLE subnet.read_assignments_after reads the section again, reverify_drift recomputes, then
fail_if_still_drifted fails the run if a target is still wrong after an approved write.Network plus platform engineers who generate firewall rules, routing policy or device templates out of phpIPAM. If a subnet's VLAN or VRF silently differs from the design, every artifact generated from it targets the wrong segment while IPAM reports no error.
The detection is a join between a file of record plus a live API, the correction is a write that nobody should make unattended, the proof is a second read. Kestra carries the inputs, the approval, the serialised writes plus the failing re-verify in one versioned flow, with the schedule already written for when the design is trusted enough to run daily.
Run against a real phpIPAM instance with six subnets in one section: two matching the design as controls, one with a drifted VLAN, one with a drifted VRF, one drifted but protected, one absent from the design. The design also named a subnet phpIPAM does not have. Nine runs, every outcome confirmed by reading phpIPAM's own API plus its database, never the Kestra log alone.
| path | result |
|---|---|
| design matches reality | 5 entries, 6 subnets read, 0 findings, nothing written |
| VLAN plus VRF drifted | 2 WRITABLE, 1 PROTECTED, 1 MISSING, every old-to-new value named |
dry_run: true |
reported only, phpIPAM identical before plus after |
| approved Pause | subnet 13 VLAN 999 to 103, subnet 14 VRF 299 to 202, re-read confirmed 0 left |
| declined Pause | no write task ran, both targets still drifted, run passed |
| protected subnet | drifted 888 against 105, reported PROTECTED, left at 888 |
| subnet absent from design | never evaluated, never written |
| empty design | refused before any phpIPAM call |
| unparseable design | refused before any phpIPAM call |
| ceiling exceeded | refused at 2 findings against a ceiling of 1, report still written |
| write refused by phpIPAM | the API answered 401, the task failed, the run failed |
| re-verify disagreement | corrections applied, phpIPAM still disagreed, run failed |
| failure path | the errors block posted to Slack on all five failing runs |
Controls stayed byte identical through every run: subnet 11 at VLAN 101 VRF 201, subnet 12 at VLAN 102 VRF 201, subnet 16 at VLAN 105 VRF 299. Subnet descriptions survived the corrections, so the write touches the assignment fields only.
A phpIPAM instance with the API enabled under Administration, API, an application whose permissions allow read plus write, the declared design for the section.
PHPIPAM_APP_TOKEN: the API token for that application. It is the default of the
app_token input, so Kestra renders it only when the input is left unset.SLACK_WEBHOOK_URL: incoming webhook for the failure alert.base_url: root URL of the phpIPAM instance.app_id: the API application identifier.app_token: API token, defaulting to the secret above.section_id: the section whose subnets are audited.declared_baseline: the design, a JSON array of {"subnetId","vlanId","vrfId"}. Required.protected_subnet_ids: comma separated IDs reported but never written.max_corrections_per_run: ceiling above which the run refuses. Default 5.dry_run: report only. Default true.auto_remediate: allow corrections at all. Default false.insecure_tls: skip certificate validation. Default false.PHPIPAM_APP_TOKEN plus SLACK_WEBHOOK_URL secrets.declared_baseline set to your design for one section, leaving dry_run true.
Read log_drift_report.protected_subnet_ids.auto_remediate: true plus dry_run: false, read the pause, answer approved
true. log_reverify reports what phpIPAM holds afterwards.io.kestra.plugin.git.Clone, so the
source of truth is reviewed the way code is.ipam.subnet.Update for ipam.vlan.Update or ipam.vrf.Update to hold VLAN names or
VRF route distinguishers to a design as well.ipam.subnet.List over several sections, then compare each against its own design file.This blueprint was created by zkasuran.
