Design: group changes and the write fence¶
Status: a proposal. Nothing here is built. The group tools are on the write list in tool_policy.yaml so the
guard refuses them, and none is under propose, so none can be proposed. This page says what has to be true before
one can be.
Why groups need their own rule¶
Epiphan Edge teams can organize devices into groups, and the groups can sit inside other groups. A device can belong to more than one group. Changing a group changes who can see a device and what a policy scope reaches, and one change can touch many devices at once. The approved-writes design fences writes by device, so a group change needs a rule of its own.
What the current fence already does¶
- Writes use the sandbox sign-in, which reaches the sandbox team only. A group ID that sign-in can name belongs to the sandbox team.
- If
FLEETWATCH_WRITE_TEAM_IDis set, the executor also checks the team the sign-in reports.
What it doesn't do: stop a group change from touching a device that is in the sandbox team but not on
FLEETWATCH_WRITE_DEVICE_IDS. Moving or deleting a group can change the membership of devices that were never
meant for testing. The device allowlist can't see that, because the tool's arguments name a group.
Proposed rule¶
- A group allowlist.
FLEETWATCH_WRITE_GROUP_IDSlists the sandbox groups a change may name. Like the other fence settings, it fails closed: with no group allowlist, no group tool can be proposed or run. - Every device a change touches must be in the device fence too. For a change that names devices
(
add_devices_to_group,remove_devices_from_groups,move_devices_to_group), each device ID must pass the existing device check. For a change that names only groups (rename_group,move_group,delete_groups), the executor reads the members of each group (get_group_devices) first and refuses unless every member passes the device check. - Both ends of a move.
move_groupandmove_devices_to_groupcheck the source and the destination against the group allowlist.create_groupchecks the parent. A group with no parent (a root group) is refused. - A fresh read, as for every write. The executor reads
get_groupsandget_group_deviceswith the sandbox sign-in right before the change, and refuses if a group isn't there, has moved, or has members that weren't on the card. - One group per proposal.
max_targetsstays at 1.delete_groupstakes a list, but a proposal names one group. - The card shows the path. The approval card shows the group's name and where it sits in the tree, the devices affected by name and ID, and for a removal whether it removes the device from this group only or from every group. Group names are untrusted text, like device names.
Order of work¶
Calm changes first, because they add structure without removing any: create_group, rename_group, then
add_devices_to_group, which keeps a device's other memberships. The changes that take things away or move them
(remove_devices_from_groups, move_devices_to_group, move_group, delete_groups) stay on the disruptive list
until the calm ones have run on the sandbox team. Each tool gets a reviewed schema with a version, as the others do.
What reading groups needs¶
Fleetwatch already scopes by a device's group (scope.groups), matching the single group ID on the device. With
nested groups and more than one membership per device, that match can miss devices. The group read tools
(get_groups, get_group_devices, and the group filter on get_devices_in_my_team) are on the read list, but
nothing calls them yet.
Open until a sandbox read:
- The shape of
get_groupsandget_group_devicesresults, and whether a device in the device list carries every group it belongs to or only one. - Whether
scope.groupsshould include a group's subgroups. The tool'sgroupfilter does. - Whether a limited-access sign-in changes what these tools return.
The parsers will be written from a captured, anonymized result, as the others were, and not before. Fixtures use neutral names and made-up IDs.
Tests the group fence needs¶
- A group change is refused with no group allowlist, a group outside it, or a member outside the device fence.
- A move is refused when either end is outside the allowlist.
- A group that changed between the card and the approval is refused, and the approval is spent.
guard()still refuses every group tool through the read-only client.