Administration guide¶
How to set this plugin up for a team: who gets which permissions, how to build the policies that govern your changes, and how to check the whole thing works before you rely on it.
If you only want the list of permission names, Permissions is the short reference. This page is the guide.
Prerequisites¶
Before granting anything:
- netbox-branching is installed and working, and your team already knows how to create a branch and make changes inside it.
- This plugin is installed,
exempt_modelsis set, and the migrations have run. See Installation and configuration. - You are comfortable with NetBox's own permission system. This plugin adds no permission machinery of its own; it uses NetBox object permissions exactly as every other model does.
Permissions¶
How permissions work here¶
This plugin does not invent a permission model. It declares the standard view, add, change and delete actions on each of its models, plus four custom actions, and NetBox enforces them.
That has one consequence worth stating plainly, because it decides how you should grant them:
Important
A NetBox object permission applies to every object of that type. Granting netbox_change_control.delete_review lets that user delete any review, not only their own. This is standard NetBox behaviour, not a quirk of this plugin. If you want a narrower grant, add a constraint to the permission. See narrowing a permission with a constraint.
Deleting somebody's review changes the outcome of the gate: removing a Request changes review removes the rejection, and the request moves off Rejected. So delete_review is a privileged grant. Treat it the way you would treat the ability to close a ticket on someone else's behalf.
Note
Editing a review is different, and deliberately so. A reviewer may edit only their own review whatever permissions they hold; a superuser may edit any. A review is one person's statement about one change, so reassigning or rewriting somebody else's would forge their position. Only the delete action follows the plain NetBox model.
Permission matrix¶
Three roles cover most deployments. Build them as NetBox groups and assign object permissions to the group rather than to individual users.
- Contributor opens branches and change requests, and comments on changes. Cannot approve.
- Reviewer everything a contributor can do, plus submitting reviews.
- Administrator manages policies and checks, and holds the two exemptions.
Branch permissions¶
These belong to netbox-branching, not to this plugin, but a change request is worthless without them.
| Permission | Contributor | Reviewer | Administrator |
|---|---|---|---|
netbox_branching.view_branch |
yes | yes | yes |
netbox_branching.add_branch |
yes | yes | yes |
netbox_branching.change_branch |
yes | yes | yes |
netbox_branching.sync_branch |
yes | yes | yes |
netbox_branching.merge_branch |
no | no | yes |
netbox_branching.view_changediff |
yes | yes | yes |
Note
NetBox reads a permission name as <app_label>.<action>_<model>, splitting on the last underscore. So branching's merge action on the Branch model is written netbox_branching.merge_branch, and its sync action is netbox_branching.sync_branch. The action you tick in the permission form is merge, not merge_branch.
view_changediff is what gates the Changes tab. Without it a reviewer cannot see what they are being asked to approve.
Change request permissions¶
| Permission | Contributor | Reviewer | Administrator |
|---|---|---|---|
netbox_change_control.view_changerequest |
yes | yes | yes |
netbox_change_control.add_changerequest |
yes | yes | yes |
netbox_change_control.change_changerequest |
yes | yes | yes |
netbox_change_control.delete_changerequest |
no | no | yes |
netbox_change_control.override_window_changerequest |
no | no | optional |
netbox_change_control.abandon_changerequest |
no | no | yes |
netbox_change_control.reopen_changerequest |
no | no | yes |
Deleting a change request destroys the record of who approved what. Keep it with the administrators.
status is not editable, by anybody. It is derived from the policy evaluation, so it is read-only on the REST API and absent from the bulk edit form. Abandon and Reopen are the two transitions a person makes by hand, and each has its own permission and its own button on the change request page. Grant them to whoever is allowed to call off a change. Contributors do not need them to give up on their own work; they can delete a draft they own, or ask an administrator.
Review permissions¶
| Permission | Contributor | Reviewer | Administrator |
|---|---|---|---|
netbox_change_control.view_review |
yes | yes | yes |
netbox_change_control.add_review |
no | yes | yes |
netbox_change_control.change_review |
no | yes | yes |
netbox_change_control.delete_review |
no | no | yes |
add_review is the one that separates a reviewer from a contributor. A user without it sees the reason in place of the review form rather than a form that fails on submit.
change_review only ever lets a person edit their own review, whoever holds it.
delete_review is model-wide. See the warning above, and consider a constraint.
Comment permissions¶
| Permission | Contributor | Reviewer | Administrator |
|---|---|---|---|
netbox_change_control.view_changecomment |
yes | yes | yes |
netbox_change_control.add_changecomment |
yes | yes | yes |
netbox_change_control.change_changecomment |
yes | yes | yes |
netbox_change_control.delete_changecomment |
no | no | yes |
change_changecomment is what allows resolving and reopening a thread. If you use the threads-resolved check, everybody who might need to unblock a merge needs it.
Check permissions¶
| Permission | Contributor | Reviewer | Administrator |
|---|---|---|---|
netbox_change_control.view_mergecheck |
yes | yes | yes |
netbox_change_control.add_mergecheck |
no | no | yes |
netbox_change_control.change_mergecheck |
no | optional | yes |
netbox_change_control.delete_mergecheck |
no | no | yes |
change_mergecheck does two things: it shows the Re-run checks button, and it is what a CI token needs to report a result over the REST API. Give the token its own user and its own permission, constrained if you can, rather than reusing a person's.
Tip
A reporting token cannot weaken the gate even with this permission. required is read-only on the API, and the gate reads requiredness from the configuration and the policies rather than from the stored row.
Policy permissions¶
| Permission | Contributor | Reviewer | Administrator |
|---|---|---|---|
netbox_change_control.view_policy |
yes | yes | yes |
netbox_change_control.view_policyrule |
yes | yes | yes |
netbox_change_control.add_policy |
no | no | yes |
netbox_change_control.change_policy |
no | no | yes |
netbox_change_control.delete_policy |
no | no | yes |
netbox_change_control.add_policyrule |
no | no | yes |
netbox_change_control.change_policyrule |
no | no | yes |
netbox_change_control.delete_policyrule |
no | no | yes |
netbox_change_control.bypass_policy |
no | no | optional |
Give everybody view_policy and view_policyrule. A reviewer who cannot read the policy cannot tell why they were asked, and the approval panel names rules the reader may not be able to open.
Editing a policy changes who must approve every open change request bound to it. That is the whole gate, so keep it with the administrators.
The two exemptions¶
Both are custom actions, and both are optional.
| Permission | Grants | Grant it to |
|---|---|---|
netbox_change_control.bypass_policy |
Write outside a branch while protect_main is enabled. |
Automation accounts, and the people who run an incident. |
netbox_change_control.override_window_changerequest |
Merge a change request outside its change window. | Whoever is allowed to break a change freeze. |
To grant them, go to Administration > Permissions > Add, and enter the action in the additional actions field rather than ticking view, add, change or delete:
| Exemption | Object type | Action to enter |
|---|---|---|
| Write directly to main | Change Control > Policy | bypass |
| Merge outside the change window | Change Control > Change Request | override_window |
Important
The trailing part of a custom permission name has to be a real model name, because NetBox splits on the last underscore. That is why the bypass lives on Policy and the window override on ChangeRequest, rather than on names that would read better.
Note
Superusers hold every permission, so they are exempt from protect_main and from every change window without being granted anything.
Setting up groups¶
- Go to Administration > Groups and create
Change Contributors,Change ReviewersandChange Administrators. - Go to Administration > Permissions > Add. Give the permission a name, tick the actions, choose the object types, and assign it to a group.
- Group one permission per area rather than making one giant permission.
Change control: reviews,Change control: policiesand so on are far easier to audit later. - Add users to groups. Do not assign object permissions directly to users except for service accounts.
The reviewer groups you name in a policy rule are the same NetBox groups. A user must be in the group and hold add_review for their approval to count: the group decides eligibility, the permission decides whether they can act at all.
Narrowing a permission with a constraint¶
This is a NetBox feature, not something this plugin adds. Every object permission can carry a constraint, a JSON queryset filter applied to the objects it covers, and the token $user in one resolves to the signed-in user. NetBox uses it itself: bookmarks and notifications are constrained with {"user": "$user"} in its own settings.
What is specific to this plugin is which field name to use for which model. The table below names the real field on each, so the filter matches rather than silently matching nothing.
To let reviewers delete their own reviews and nobody else's, create a permission with the delete action on Change Control > Review and this constraint:
$user resolves to the signed-in user. The same pattern works elsewhere:
| Goal | Object type | Constraint |
|---|---|---|
| Delete only my own reviews | Review | {"reviewer": "$user"} |
| Delete only my own comments | Change Comment | {"author": "$user"} |
| Manage only my own change requests | Change Request | {"requester": "$user"} |
| Report results for one check only | Merge Check | {"name": "ci-pipeline"} |
Tip
The last one is the right shape for a CI token. It can report the check it owns and touch nothing else.
Configuring policies¶
Creating a policy¶
Change Control > Policies > Add.
| Field | What to put in it |
|---|---|
| Name | What it governs, in your team's language. Circuit changes, not Policy 3. |
| Enabled | Leave ticked. A disabled policy is never attached to anything. |
| Weight | Display and evaluation order. Lower is listed first. Leave at 1000 unless you care. |
| Object types | The types this policy governs. Leave empty to match every branch, which is how you build a baseline. |
| Conditions | Optional. Narrows on the values of the changed objects. See Policy conditions. |
| Condition state | Which side of the change the conditions read. The default reads both. |
| Registered checks | Opt-in checks to require wherever this policy applies. |
| Reported checks | Names your own systems report over the REST API. |
Policies attach on their own, from the object types the branch touches, and they keep following the branch as it changes. The author cannot pick them and cannot remove them. See policies attach automatically.
Configuring policy rules¶
A policy with no rules asks for nothing, so add at least one. Change Control > Policy Rules > Add.
| Field | What to put in it |
|---|---|
| Policy | The policy this rule belongs to. |
| Name | What the requirement is. Two engineers, One lead. This name is shown to reviewers. |
| Minimum reviews | How many eligible people must approve. Zero means none; the checks become the only gate. |
| Reviewer groups | Members of any listed group may satisfy it. |
| Reviewers | Individually named users may satisfy it. |
A user satisfies a rule if they are in any of its groups or are named on it. A policy is satisfied only when every one of its rules is.
An approval counts only toward the rules the approver is eligible for. A lead approving does not advance a rule that asks for engineers, which is the property a plain approval counter gets wrong.
The pre-merge gate¶
Once the plugin is installed, a branch cannot merge unless it carries an approved change request whose policies are still satisfied, every required check passes, and the change window is open. The button is hidden rather than failing after the click, because the gate is registered as a netbox-branching pre-action validator.
Set enforce_merge_gate to False only to troubleshoot. It turns off the reason the plugin exists.
Policy configuration examples¶
Single approval, everywhere. The baseline every deployment should start with.
| Field | Value |
|---|---|
| Object types | (empty) |
| Registered checks | has-changes, no-conflicts, not-stale |
| Rule | One engineer, minimum reviews 1, group Change Engineers |
Two-person rule. For anything that can take a site off the air.
| Field | Value |
|---|---|
| Object types | dcim.device, dcim.interface |
| Rule | Two engineers, minimum reviews 2, group Change Engineers |
Senior approval on top. Two rules on one policy, so both must be met.
| Field | Value |
|---|---|
| Object types | circuits.circuit, circuits.circuittermination |
| Rule 1 | One engineer, minimum reviews 1, group Change Engineers |
| Rule 2 | One lead, minimum reviews 1, group Change Leads |
Live objects only. The same policy, narrowed so a planned circuit stays a one-person change.
| Field | Value |
|---|---|
| Object types | circuits.circuit |
| Conditions | {"attr": "status", "value": "active"} |
| Condition state | Either side of the change |
No human at all. For scripted, low-risk work that must still pass the machine gate.
| Field | Value |
|---|---|
| Object types | ipam.prefix, ipam.ipaddress |
| Registered checks | has-changes, no-conflicts, not-stale |
| Rule | No approval required, minimum reviews 0 |
Warning
A zero rule removes the human requirement of its own policy only. If your baseline policy also matches the branch and asks for an engineer, the change still waits for that engineer. Scope the automatic policy so it is the only one matching, or narrow the baseline with conditions.
Testing the workflow¶
Do this once, on a test instance, before you rely on any of it.
- Create the three groups and their permissions.
- Create the baseline policy with one rule asking for one engineer.
- Create a second policy scoped to
dcim.device, asking for one lead. - Sign in as a contributor. Create a branch and change a prefix inside it.
- Open a change request against that branch and submit it for review.
- Confirm the Applied policies card lists the baseline only, and the checks have run.
- Sign in as a reviewer and approve. Confirm the status becomes Approved and the merge button appears for whoever holds
merge_branch. - Back as the contributor, edit a device inside the same branch.
- Confirm the request drops back to Needs review, and that the device policy has now attached and is asking for a lead. This is the check that the gate follows the branch rather than freezing at submission.
- Approve as a lead, then merge. Confirm the request becomes Completed.
Steps 8 and 9 are the ones worth repeating after any upgrade.
Troubleshooting¶
The merge button is greyed out and the reason says the request is not approved. The people gate and the machine gate are separate. Read the Approval status card: a rule showing 0/1 names who may satisfy it.
A reviewer's approval did not count. Three usual causes. They are not in a group the rule names. They approved, then the branch changed and their review went stale, which is shown with a Stale badge. Or they approved a different rule's requirement and the one still short asks for somebody else.
The approval panel says no policy rules apply. No enabled policy matched the branch, or every matching policy has no rules. A request with no rules is never satisfied, on purpose: an unpoliced merge is what the plugin exists to prevent. Add a baseline policy with no object types.
Checks sit on pending forever. A name that is not a registered check is treated as reported from outside, and waits for something to report it. Either something must PATCH a result, or the name is a typo in the policy's Reported checks field.
A conflict is reported that you believe you already resolved. netbox-branching never advances a diff's baseline, so a field main touched before your last sync stays flagged. This plugin distinguishes the two and says so on the request. See Conflicts with main.
A user cannot see the Changes tab. They are missing netbox_branching.view_changediff.
protect_main is blocking a script. Writes with no request context are allowed, so a management command or a background job is not affected. An interactive write needs netbox_change_control.bypass_policy.
A change with a window never merges automatically. The sweep runs every auto_merge_interval minutes and can step over a shorter window. The request warns about this itself. See the sweep interval.