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
@@ -164,6 +164,7 @@ func (c *checker) subnets(st *model.State, env *Env) {
|
||||
c.leaseTime(s.LeaseTime, obj)
|
||||
}
|
||||
c.pools(s, obj)
|
||||
c.access(st, s, obj)
|
||||
c.options(s.Options, obj)
|
||||
c.routers(s, st.Settings.Options, obj)
|
||||
if env != nil && !s.Disabled && s.Interface != "" {
|
||||
@@ -206,6 +207,40 @@ func (c *checker) pools(s *model.Subnet, obj string) {
|
||||
}
|
||||
}
|
||||
|
||||
// access checks a subnet's access mode and lists.
|
||||
func (c *checker) access(st *model.State, s *model.Subnet, obj string) {
|
||||
if s.Access != "" && s.Access != model.AccessDenyList && s.Access != model.AccessAllowList {
|
||||
c.add(Error, "access_mode", obj, "the access %q is not deny-list or allow-list", s.Access)
|
||||
}
|
||||
for _, list := range []struct {
|
||||
name string
|
||||
entries []model.ClientMatch
|
||||
}{{"allow", s.Allow}, {"deny", s.Deny}} {
|
||||
seen := map[string]bool{}
|
||||
for _, m := range list.entries {
|
||||
if norm, err := model.ParseClientMatch(m.Match); err != nil || norm != m.Match {
|
||||
c.add(Error, "access_entry", obj, "the %s list entry %q is not a MAC address (aa:bb:cc:dd:ee:ff), a prefix (aa:bb:cc:*) or a client ID (id 01aabbccddeeff)", list.name, m.Match)
|
||||
} else if seen[m.Match] {
|
||||
c.add(Error, "access_entry_duplicate", obj, "%s is on the %s list twice", m.Match, list.name)
|
||||
}
|
||||
seen[m.Match] = true
|
||||
}
|
||||
}
|
||||
reserved := 0
|
||||
for _, r := range st.Reservations {
|
||||
if r.Disabled || !s.Prefix.Contains(r.IP) {
|
||||
continue
|
||||
}
|
||||
reserved++
|
||||
if ok, _ := s.Admits(r.MAC, r.ClientID, true); !ok {
|
||||
c.add(Warning, "reservation_denied", obj, "the client of reservation %s is on the deny list: it gets no address", r.Label())
|
||||
}
|
||||
}
|
||||
if s.Access == model.AccessAllowList && len(s.Allow) == 0 && reserved == 0 && !s.Disabled {
|
||||
c.add(Warning, "allow_list_empty", obj, "the subnet answers only clients on its allow list or with a reservation, and it has neither: nobody gets an address")
|
||||
}
|
||||
}
|
||||
|
||||
// routers warns about a gateway outside the subnet: clients can't reach it.
|
||||
func (c *checker) routers(s *model.Subnet, global []model.OptionValue, obj string) {
|
||||
val := ""
|
||||
|
||||
Reference in new issue
Block a user