Return icon
Fail icon
List icon
Log icon
If icon
Pause icon
Loop icon
Update icon
SlackIncomingWebhook icon
Schedule icon

Detect then correct phpIPAM subnet VLAN plus VRF assignment drift

Compare phpIPAM subnet VLAN plus VRF assignments against a declared design, correct the drift behind an approval gate, then re-read to prove it.

Categories
Infrastructure

Findings

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.

Safety

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:

  • the design is a required input, never inferred from what phpIPAM currently holds
  • an empty or unparseable design refuses the run before any phpIPAM call
  • a subnet absent from the design is never evaluated, never written
  • a baseline entry naming a subnet phpIPAM does not have is reported MISSING, never corrected
  • protected_subnet_ids subnets are reported when they drift, never written
  • more drift than max_corrections_per_run refuses the whole correction
  • every write needs auto_remediate true, dry_run false plus a Pause answered true

How it works

  1. parse_baseline counts the usable entries in declared_baseline. An unparseable design fails this task under allowFailure, which is what the refusal reads.
  2. refuse_unusable_baseline stops the run when that count is zero or absent. Nothing has touched phpIPAM yet at this point.
  3. read_assignments_before lists every subnet in the section with its live vlanId plus vrfId.
  4. 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.
  5. log_drift_report writes the counts, the protected skips plus the full detail to the log.
  6. refuse_over_ceiling fails when more subnets are WRITABLE than the per-run ceiling allows.
  7. 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.
  8. 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.

What you get

  • a drift report naming the subnet, its CIDR, its description plus the old-to-new VLAN or VRF
  • three classes in that report, so a protected segment reads differently from a real target
  • corrections applied one at a time through the phpIPAM API
  • a second phpIPAM read that decides whether the run passes
  • a Slack message on any failure or refusal

Who it's for

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.

Why orchestrate this with Kestra

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.

Tested end to end

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.

Prerequisites

A phpIPAM instance with the API enabled under Administration, API, an application whose permissions allow read plus write, the declared design for the section.

Secrets

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

Inputs

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

Quick start

  1. Add the PHPIPAM_APP_TOKEN plus SLACK_WEBHOOK_URL secrets.
  2. Run it with declared_baseline set to your design for one section, leaving dry_run true. Read log_drift_report.
  3. Fix the design until the only rows left are real drift. Put anything a change window owns into protected_subnet_ids.
  4. Re-run with auto_remediate: true plus dry_run: false, read the pause, answer approved true. log_reverify reports what phpIPAM holds afterwards.
  5. Enable the schedule once the design is trusted. Its inputs keep it report only.

How to extend

  • Read the design out of Git instead of an input with io.kestra.plugin.git.Clone, so the source of truth is reviewed the way code is.
  • Swap ipam.subnet.Update for ipam.vlan.Update or ipam.vrf.Update to hold VLAN names or VRF route distinguishers to a design as well.
  • Add ipam.subnet.List over several sections, then compare each against its own design file.
  • Post the report to the network team's channel on every run, not only on failure.

Links

This blueprint was created by zkasuran.

See How

New to Kestra?

Use blueprints to kickstart your first workflows.