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
+27
-6
@@ -25,16 +25,16 @@ func (s *Store) LoadState(ctx context.Context) (*model.State, error) {
|
||||
if st.Settings.Options == nil {
|
||||
st.Settings.Options = []model.OptionValue{}
|
||||
}
|
||||
rows, err := s.db.QueryContext(ctx, `SELECT id, name, interface, prefix, pools, pool_order, address_order, lease_time, options, disabled, comment FROM subnets ORDER BY id`)
|
||||
rows, err := s.db.QueryContext(ctx, `SELECT id, name, interface, prefix, pools, pool_order, address_order, lease_time, options, access, allow_list, deny_list, disabled, comment FROM subnets ORDER BY id`)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
for rows.Next() {
|
||||
sn := &model.Subnet{}
|
||||
var prefix, pools, opts string
|
||||
var prefix, pools, opts, allow, deny string
|
||||
var disabled int
|
||||
if err := rows.Scan(&sn.ID, &sn.Name, &sn.Interface, &prefix, &pools, &sn.PoolOrder, &sn.AddressOrder, &sn.LeaseTime, &opts, &disabled, &sn.Comment); err != nil {
|
||||
if err := rows.Scan(&sn.ID, &sn.Name, &sn.Interface, &prefix, &pools, &sn.PoolOrder, &sn.AddressOrder, &sn.LeaseTime, &opts, &sn.Access, &allow, &deny, &disabled, &sn.Comment); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
sn.Prefix, _ = netip.ParsePrefix(prefix)
|
||||
@@ -45,6 +45,18 @@ func (s *Store) LoadState(ctx context.Context) (*model.State, error) {
|
||||
if err := json.Unmarshal([]byte(opts), &sn.Options); err != nil {
|
||||
return nil, fmt.Errorf("subnet %d options: %w", sn.ID, err)
|
||||
}
|
||||
if err := json.Unmarshal([]byte(allow), &sn.Allow); err != nil {
|
||||
return nil, fmt.Errorf("subnet %d allow list: %w", sn.ID, err)
|
||||
}
|
||||
if err := json.Unmarshal([]byte(deny), &sn.Deny); err != nil {
|
||||
return nil, fmt.Errorf("subnet %d deny list: %w", sn.ID, err)
|
||||
}
|
||||
if len(sn.Allow) == 0 {
|
||||
sn.Allow = nil
|
||||
}
|
||||
if len(sn.Deny) == 0 {
|
||||
sn.Deny = nil
|
||||
}
|
||||
st.Subnets = append(st.Subnets, sn)
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
@@ -146,10 +158,12 @@ func applyChange(ctx context.Context, tx *sql.Tx, c model.Change) error {
|
||||
if err := json.Unmarshal(c.After, &sn); err != nil {
|
||||
return err
|
||||
}
|
||||
_, err := tx.ExecContext(ctx, `INSERT INTO subnets (id, name, interface, prefix, pools, pool_order, address_order, lease_time, options, disabled, comment) VALUES (?,?,?,?,?,?,?,?,?,?,?)
|
||||
_, err := tx.ExecContext(ctx, `INSERT INTO subnets (id, name, interface, prefix, pools, pool_order, address_order, lease_time, options, access, allow_list, deny_list, disabled, comment) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?)
|
||||
ON CONFLICT(id) DO UPDATE SET name = excluded.name, interface = excluded.interface, prefix = excluded.prefix, pools = excluded.pools,
|
||||
pool_order = excluded.pool_order, address_order = excluded.address_order, lease_time = excluded.lease_time, options = excluded.options, disabled = excluded.disabled, comment = excluded.comment`,
|
||||
sn.ID, sn.Name, sn.Interface, sn.Prefix.String(), jsonText(nonNil(sn.Pools)), sn.PoolOrder, sn.AddressOrder, sn.LeaseTime, jsonText(nonNilOpts(sn.Options)), boolInt(sn.Disabled), sn.Comment)
|
||||
pool_order = excluded.pool_order, address_order = excluded.address_order, lease_time = excluded.lease_time, options = excluded.options,
|
||||
access = excluded.access, allow_list = excluded.allow_list, deny_list = excluded.deny_list, disabled = excluded.disabled, comment = excluded.comment`,
|
||||
sn.ID, sn.Name, sn.Interface, sn.Prefix.String(), jsonText(nonNil(sn.Pools)), sn.PoolOrder, sn.AddressOrder, sn.LeaseTime, jsonText(nonNilOpts(sn.Options)),
|
||||
sn.Access, jsonText(nonNilMatches(sn.Allow)), jsonText(nonNilMatches(sn.Deny)), boolInt(sn.Disabled), sn.Comment)
|
||||
return err
|
||||
case model.KindReservation:
|
||||
if c.After == nil {
|
||||
@@ -176,6 +190,13 @@ func nonNil(p []model.Pool) []model.Pool {
|
||||
return p
|
||||
}
|
||||
|
||||
func nonNilMatches(m []model.ClientMatch) []model.ClientMatch {
|
||||
if m == nil {
|
||||
return []model.ClientMatch{}
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
func nonNilOpts(o []model.OptionValue) []model.OptionValue {
|
||||
if o == nil {
|
||||
return []model.OptionValue{}
|
||||
|
||||
Reference in new issue
Block a user