Allow and deny lists per subnet; every option of a DHCP message in words
- A subnet is a deny-list subnet (the default: everyone not denied) or
an allow-list subnet (only clients allowed or with a reservation);
the deny list counts in both. Entries are MAC addresses, prefixes
(aa:bb:cc:*) or client IDs. A refused client gets no answer (DENIED
in the activity log, with an Allow button) or a NAK on renewal.
steward access, the subnet's Access tab. Schema migration 7.
- The activity log keeps each message's header fields and options,
decoded into words (dhcp4.Describe): the parameter request list by
name, client IDs, lease times. ▸ on an Activity row, steward
activity show N / --details, GET /events/{id}.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
b3c8329ae8
commit
3ad2f981d4
30 files changed
+1092
-37
No files matched your search
+6
-1
@@ -99,6 +99,10 @@ Creating answers 201, other changes 200.
|
||||
"options": [{"option": "routers", "value": "10.0.10.1"}], "disabled": false, "comment": ""}
|
||||
```
|
||||
|
||||
`access` is `deny-list` (the default, left out) or `allow-list`; `allow`
|
||||
and `deny` replace the lists, each `[{"match": "aa:bb:cc:*", "comment":
|
||||
"Sonos"}]` with a MAC address, a prefix or `"id HEX"`
|
||||
([subnets.md](subnets.md#access-allow-and-deny-lists)).
|
||||
`pools` and `options` replace the lists; the pools keep their order. A
|
||||
pool's `:ORDER` is its own address order. `pool_order` is `listed` or
|
||||
`random`; `address_order` is `sequential`, `reverse`, `random` or
|
||||
@@ -122,7 +126,8 @@ out. `lease_time` 0 means the settings'.
|
||||
| `GET /leases/{ip}` | one |
|
||||
| `DELETE /leases/{ip}` | forget it (audited) |
|
||||
| `POST /leases/{ip}/reserve` | make it a reservation; optional `{"hostname", "comment", "by"}`, `by` being `mac`, `client_id` or `both` (the default) |
|
||||
| `GET /events[?mac=&subnet=&interface=&limit=]` | the latest DHCP messages, newest first (the last 1000 are kept in memory). `subnet` (ID, name or prefix) keeps messages for its addresses, and those without an address on its interface |
|
||||
| `GET /events[?mac=&subnet=&interface=&limit=&details=1]` | the latest DHCP messages, newest first (the last 1000 are kept in memory), each with an `id`, `direction` (`in` from the client, `out` from Steward) and `client_id`. `subnet` (ID, name or prefix) keeps messages for its addresses, and those without an address on its interface. `details=1` adds `fields` and `options` |
|
||||
| `GET /events/{id}` | one message with `fields` (header fields that are set) and `options`, each `{"code", "name", "value"}` in words |
|
||||
|
||||
### History and import
|
||||
|
||||
|
||||
+37
-5
@@ -82,6 +82,18 @@ one. An option Steward doesn't know: `--option 224:string=hello`.
|
||||
| `reservation enable\|disable RES` | use a reservation, or keep it but ignore it |
|
||||
| `reservation delete RES` | |
|
||||
|
||||
### access
|
||||
|
||||
| Command | |
|
||||
|---|---|
|
||||
| `access show SUBNET` | the subnet's mode and its allow and deny lists |
|
||||
| `access mode SUBNET deny-list\|allow-list` | answer every client not denied, or only the allowed (and reserved) |
|
||||
| `access allow SUBNET ENTRY... [--comment C]` | add to the allow list (`aa:bb:cc:dd:ee:ff`, `aa:bb:cc:*` or `id HEX`); an entry it has gets the new comment |
|
||||
| `access deny SUBNET ENTRY... [--comment C]` | add to the deny list |
|
||||
| `access remove SUBNET ENTRY...` | take off both lists |
|
||||
|
||||
See [subnets.md](subnets.md#access-allow-and-deny-lists).
|
||||
|
||||
### lease
|
||||
|
||||
| Command | |
|
||||
@@ -93,19 +105,39 @@ one. An option Steward doesn't know: `--option 224:string=hello`.
|
||||
|
||||
### activity
|
||||
|
||||
`steward activity [--mac MAC] [--subnet SUBNET] [--interface I] [--limit N] [-f]`:
|
||||
`steward activity [--mac MAC] [--subnet SUBNET] [--interface I] [--limit N] [-d] [-f]`:
|
||||
the latest DHCP messages, received and sent, oldest first. `--subnet`
|
||||
takes an ID, name or prefix; a message without an address (a DISCOVER,
|
||||
"no address") shows for every subnet on its interface. `-f` keeps
|
||||
watching, which is useful while a device tries to get an address:
|
||||
|
||||
```
|
||||
11:02:13.120 eth0 DHCPDISCOVER 3c:22:fb:11:22:33 - adams-macbook
|
||||
11:02:13.622 eth0 DHCPOFFER 3c:22:fb:11:22:33 10.0.10.150 adams-macbook
|
||||
11:02:13.630 eth0 DHCPREQUEST 3c:22:fb:11:22:33 10.0.10.150 adams-macbook
|
||||
11:02:13.631 eth0 DHCPACK 3c:22:fb:11:22:33 10.0.10.150 adams-macbook
|
||||
#1042 11:02:13.120 eth0 DHCPDISCOVER 3c:22:fb:11:22:33 - adams-macbook
|
||||
#1043 11:02:13.622 eth0 DHCPOFFER 3c:22:fb:11:22:33 10.0.10.150 adams-macbook
|
||||
#1044 11:02:13.630 eth0 DHCPREQUEST 3c:22:fb:11:22:33 10.0.10.150 adams-macbook
|
||||
#1045 11:02:13.631 eth0 DHCPACK 3c:22:fb:11:22:33 10.0.10.150 adams-macbook
|
||||
```
|
||||
|
||||
Each line starts with the message's number. `-d` (`--details`) adds every
|
||||
field and option of each message in words, and `steward activity show N`
|
||||
shows one: what the client asked for (its parameter request list, by
|
||||
name), its client identifier and host name, and what Steward sent back
|
||||
(lease time, routers, DNS servers...):
|
||||
|
||||
```
|
||||
$ steward activity show 1042
|
||||
#1042 DHCPDISCOVER on eth0 at 2026-10-18 11:02:13.120, received from the client
|
||||
transaction ID: 0x3a1c99f0
|
||||
client hardware address: 3c:22:fb:11:22:33
|
||||
flags: broadcast (the client wants replies broadcast)
|
||||
option 53 message-type: DHCPDISCOVER
|
||||
option 61 client-identifier: 01:3c:22:fb:11:22:33 (type 1: the MAC address 3c:22:fb:11:22:33)
|
||||
option 55 parameter-request-list: subnet-mask (1), routers (3), domain-name-servers (6), domain-name (15), v4-captive-portal (114)
|
||||
option 12 host-name: adams-macbook
|
||||
```
|
||||
|
||||
In the web UI, ▸ on an Activity row opens the same.
|
||||
|
||||
`NO ADDRESS` means the pools on that interface are full, and `CONFLICT`
|
||||
an address that answered the ping check. A `DHCPNAK` says why it was
|
||||
sent.
|
||||
|
||||
@@ -201,6 +201,45 @@ renewed, and is kept across restarts.
|
||||
`steward lease delete IP` frees an address at once. A client still using
|
||||
it gets a NAK when it next renews, then asks for a new address.
|
||||
|
||||
## Access: allow and deny lists
|
||||
|
||||
A subnet decides who it answers:
|
||||
|
||||
* **deny-list** (the default): every client except those on its deny
|
||||
list;
|
||||
* **allow-list**: only clients on its allow list or with a reservation in
|
||||
it, and not on its deny list.
|
||||
|
||||
The deny list counts in both modes, and wins over a reservation; so an
|
||||
allow-list subnet can allow a maker's devices by prefix and still deny one
|
||||
of them. Switching the mode keeps both lists.
|
||||
|
||||
Entries are a MAC address (`aa:bb:cc:dd:ee:ff`), a prefix for one maker's
|
||||
devices (`aa:bb:cc:*`, 1 to 5 bytes) or a client identifier
|
||||
(`id 01aabbccddeeff`), each with an optional comment:
|
||||
|
||||
```sh
|
||||
steward access mode iot allow-list
|
||||
steward access allow iot aa:bb:cc:* --comment "Sonos speakers"
|
||||
steward access deny office 3c:22:fb:11:22:33 --comment "old laptop"
|
||||
steward access remove office 3c:22:fb:11:22:33
|
||||
steward access show iot
|
||||
```
|
||||
|
||||
In the web UI it is a subnet's **Access** tab; a DENIED line in Activity
|
||||
has an **Allow** button that puts that client on the subnet's allow list.
|
||||
|
||||
A refused client gets **no answer**: its DISCOVER shows as `DENIED` in the
|
||||
activity log, with the reason. A client refused after it got its lease
|
||||
gets a **NAK** when it next renews, so it stops using the address then
|
||||
(within half its lease time).
|
||||
|
||||
This is a control, not security: anyone can copy an allowed MAC address.
|
||||
Phones and laptops also use a **private MAC address** per network by
|
||||
default (iOS, Android, Windows), different from the one on their label;
|
||||
on an allow-list subnet, turn that off for the network on the device, or
|
||||
allow the private address it shows (Activity lists it).
|
||||
|
||||
## Authority
|
||||
|
||||
Steward is **authoritative** for its interfaces. A client that asks to
|
||||
|
||||
@@ -63,6 +63,11 @@ These apply to options anywhere (settings, subnets, reservations).
|
||||
| `reservation_network_address` | error | the network or broadcast address of its subnet |
|
||||
| `reservation_ip_taken` | error | the address is reserved already |
|
||||
| `reservation_client_taken` | error | the client has a reservation in this subnet already (one per subnet; a device on two VLANs may have one in each) |
|
||||
| `reservation_denied` | warning | a reservation's client is on its subnet's deny list, so it gets no address |
|
||||
| `access_mode` | error | a subnet's access is not `deny-list` or `allow-list` |
|
||||
| `access_entry` | error | an allow or deny list entry that isn't a MAC address, a prefix (`aa:bb:cc:*`) or `id HEX` |
|
||||
| `access_entry_duplicate` | error | an entry is on the same list twice |
|
||||
| `allow_list_empty` | warning | an allow-list subnet with no allow list entries and no reservations: nobody gets an address |
|
||||
| `reservation_hostname` | error | not a host name (letters, digits and `-`, labels up to 63 characters) |
|
||||
| `reservation_server_address` | warning, host | the server's own address |
|
||||
|
||||
|
||||
Reference in new issue
Block a user