List icon
Return icon
Switch icon
Log icon
SlackIncomingWebhook icon
Schedule icon

phpIPAM Unlabeled Address Hygiene Gate

Audit a phpIPAM subnet for in-use addresses with no hostname or description and alert Slack naming the real IPs found.

Categories
CloudInfrastructure

An IP address marked in-use in phpIPAM with no hostname and no description is a ghost allocation, something is consuming that address, the record even says so, but nobody looking at phpIPAM today can tell what it is or who owns it. Subnet capacity dashboards do not catch this, a fully-utilized subnet full of unlabeled addresses looks exactly as healthy as one full of well-documented ones. This blueprint lists a subnet's addresses on a schedule, finds the real ones marked in-use with both fields blank, and alerts Slack naming the exact IPs, never an invented count.

How it works

  1. probe_subnet_addresses (io.kestra.plugin.phpipam.ipam.address.List) reads every address recorded for subnet_id, including its tag, hostname, and description, with allowFailure: true so a bad URL, app ID, or token does not stop the flow before it can be classified as PROBE_FAILED below.
  2. classify_address_hygiene_status (io.kestra.plugin.core.debug.Return) returns PROBE_FAILED when the probe did not return at all, CRITICAL or WARNING from the real count of addresses where tag equals used_tag_id and both hostname and description are blank, against the two thresholds, HEALTHY otherwise.
  3. route_by_address_hygiene_status (io.kestra.plugin.core.flow.Switch) branches on that status: HEALTHY logs it, the other three each alert Slack, the WARNING and CRITICAL alerts name the real unlabeled IPs found, never an invented list. An unexpected value falls through to a defaults branch that logs it rather than being silently dropped.
  4. log_audit_summary always runs last and logs the final classification.
  5. The errors block posts to Slack if the flow itself fails outside the gate logic above.

What you get

  • A real list of exactly which IPs are consuming address space with zero identifying information, not a guess from overall subnet utilization.
  • A Slack alert naming the actual offending IPs, never an invented count.
  • A severity tier you control, separate from phpIPAM's own address status tags.
  • A fully local test path against the official phpIPAM Docker image, no managed instance needed.

Who it's for

  • Network and infrastructure teams running phpIPAM who want address-record hygiene enforced on a schedule, not only discovered during an audit.
  • Teams evaluating the phpIPAM plugin who want a working example of ipam.address.List and the plugin's app-token authentication.

Why orchestrate this with Kestra

phpIPAM records whether an address is in use, it has no built-in alert for an in-use address with no owner or hostname attached. Kestra adds that check as a scheduled, declarative flow: the probe, the classification against your own thresholds, and the Slack alert naming the real IPs are all one auditable execution history.

Prerequisites

  • For local testing, the official phpIPAM Docker Compose stack:

    services:
      phpipam-web:
        image: phpipam/phpipam-www:latest
        ports:
          - "80:80"
        environment:
          - IPAM_DATABASE_HOST=phpipam-mariadb
          - IPAM_DATABASE_PASS=my_secret_phpipam_pass
          - IPAM_DATABASE_WEBHOST=%
        depends_on:
          - phpipam-mariadb
      phpipam-mariadb:
        image: mariadb:latest
        environment:
          - MYSQL_ROOT_PASSWORD=my_secret_mysql_root_pass
        volumes:
          - phpipam-db-data:/var/lib/mysql
    volumes:
      phpipam-db-data:
    

    Open http://localhost, finish the setup wizard, then generate an App token under Administration, API, with an application that has at least read permission.

  • A subnet with at least one address manually marked Used (tag 2 by default) and left without a hostname or description, to see WARNING/CRITICAL rather than HEALTHY.

  • A Slack incoming webhook for alerts.

Secrets

  • PHPIPAM_APP_TOKEN: the static App token generated in phpIPAM's API administration panel.

  • SLACK_WEBHOOK: Slack incoming webhook URL used by the alert tasks and the errors block.

    In Kestra OSS, set these as base64-encoded environment variables, for example SECRET_PHPIPAM_APP_TOKEN, SECRET_SLACK_WEBHOOK.

Inputs

Input Type Default Allowed values
base_url STRING http://localhost any reachable phpIPAM base URL
app_id STRING myapp an application ID configured under Administration, API
subnet_id STRING 1 any existing subnet's numeric ID
used_tag_id STRING 2 any tag ID configured on this install
unlabeled_count_warning_threshold INT 1 any non-negative integer
unlabeled_count_critical_threshold INT 10 any non-negative integer, greater than the warning threshold
insecure_tls BOOL false true, false

Quick start

  1. Start the phpIPAM stack above and generate an App token.
  2. Create a subnet, add an address, mark it Used, and leave its hostname and description blank.
  3. Add the PHPIPAM_APP_TOKEN and SLACK_WEBHOOK secrets.
  4. Run this flow manually with subnet_id set to that subnet, it should report WARNING and name the address you just created.
  5. Enable the daily trigger.

How to extend

  • Loop this flow with io.kestra.plugin.core.flow.Loop across every subnet ID returned by io.kestra.plugin.phpipam.ipam.subnet.List to audit an entire phpIPAM instance from one flow.
  • Also flag addresses with a hostname but a blank owner, a different hygiene gap than a fully blank record.
  • Join the alert against a CMDB or directory lookup by mac to attempt auto-attribution before falling back to a human-reviewed alert.

Pitfalls

  • used_tag_id defaults to phpIPAM's stock value of 2, but tags are configurable per install. Confirm yours with GET /api/{appId}/addresses/tags/ before trusting the default on an existing, customized instance.
  • This gate only reads and alerts, it never assigns a hostname or description, deciding what an unlabeled address actually is needs a human who recognizes the device, not an automated guess.
  • fetchType: FETCH loads every address in the subnet into memory at once, fine for a typical subnet, switch to STORE and process the file instead for a very large one.
  • A blank description field in phpIPAM's API can come back as an empty string or be entirely absent from the JSON response depending on how the address was created, the // "" fallback in this gate's jq expressions treats both the same way.

Links

See How

New to Kestra?

Use blueprints to kickstart your first workflows.