Implement multi-tenancy access controls: restrict users to specific areas of interest #432

Closed
opened 2025-12-29 16:21:59 +01:00 by adam · 56 comments
Owner

Originally created by @askbow on GitHub (Sep 14, 2016).

Originally assigned to: @jeremystretch on GitHub.

I've read https://github.com/digitalocean/netbox/issues/446 and thought that it would be nice to have a more general and finer access control with an ability to restrict users (user groups) to view / edit / create objects only in specific Tenants, Tenant Groups, or Sites. And such access control should be enforced in UI as well as in API access.

That way one could give some limited access for the tenants' engineers themselves, or set some internal access boundaries (for the security-conscious).

Originally created by @askbow on GitHub (Sep 14, 2016). Originally assigned to: @jeremystretch on GitHub. I've read https://github.com/digitalocean/netbox/issues/446 and thought that it would be nice to have a more general and finer access control with an ability to restrict users (user groups) to view / edit / create objects only in specific Tenants, Tenant Groups, or Sites. And such access control should be enforced in UI as well as in API access. That way one could give some limited access for the tenants' engineers themselves, or set some internal access boundaries (for the security-conscious).
adam added the status: accepted label 2025-12-29 16:21:59 +01:00
adam closed this issue 2025-12-29 16:21:59 +01:00
Author
Owner

@jsenecal commented on GitHub (Oct 18, 2016):

One way to acheive this is per-object permissions, maybe based on Django-Rules (https://github.com/dfunckt/django-rules) or similar

@jsenecal commented on GitHub (Oct 18, 2016): One way to acheive this is per-object permissions, maybe based on Django-Rules (https://github.com/dfunckt/django-rules) or similar
Author
Owner

@mryauch commented on GitHub (Oct 24, 2016):

I know this was mentioned on #323 but I'd like to add my voice. I just setup NetBox and found it is 100% what my org needs, however not having permissions at the site level is a deal breaker. With 30+ national sites we don't want to give everyone that needs to change IP prefixes at their site the ability to change prefixes across the country. Either way, awesome job Jeremy, this is an impressive and well thought out tool.

@mryauch commented on GitHub (Oct 24, 2016): I know this was mentioned on #323 but I'd like to add my voice. I just setup NetBox and found it is 100% what my org needs, however not having permissions at the site level is a deal breaker. With 30+ national sites we don't want to give everyone that needs to change IP prefixes at their site the ability to change prefixes across the country. Either way, awesome job Jeremy, this is an impressive and well thought out tool.
Author
Owner

@Armadill0 commented on GitHub (Aug 4, 2017):

This would be (if I understood aright) a useful feature to allow IT sub teams the management e.g. of their own site(s)/IP space.

@Armadill0 commented on GitHub (Aug 4, 2017): This would be (if I understood aright) a useful feature to allow IT sub teams the management e.g. of their own site(s)/IP space.
Author
Owner

@GregoryVirrel commented on GitHub (Nov 16, 2017):

Hi all.

I also strongly support this feature. This is one of the most important feature since it is about cloisoning et security.

@GregoryVirrel commented on GitHub (Nov 16, 2017): Hi all. I also strongly support this feature. This is one of the most important feature since it is about cloisoning et security.
Author
Owner

@TheGuyDanish commented on GitHub (Dec 19, 2017):

I'd love to bring this up again. I'm wanting to deploy NetBox within my organization, but a requirement from my higher ups is that there must be a way for our customers to see their components in our datacenter/network.

@TheGuyDanish commented on GitHub (Dec 19, 2017): I'd love to bring this up again. I'm wanting to deploy NetBox within my organization, but a requirement from my higher ups is that there must be a way for our customers to see their components in our datacenter/network.
Author
Owner

@Schnatterowski commented on GitHub (Jan 17, 2018):

We too are planning to give our customers access to their own racks in our datacenter spaces. I'd very much like this feature to be implemented. As it is now, we'd have to run one netbox instance per customer. It would be great if it would be possible to give user account access to one or more tenants.

BTW, great job!

@Schnatterowski commented on GitHub (Jan 17, 2018): We too are planning to give our customers access to their own racks in our datacenter spaces. I'd very much like this feature to be implemented. As it is now, we'd have to run one netbox instance per customer. It would be great if it would be possible to give user account access to one or more tenants. BTW, great job!
Author
Owner

@tduehr commented on GitHub (Jan 24, 2018):

Implementing this would allow me to use Netbox to replace teampass along with IPAM and Racktables. I need to control access to secrets along side the devices they pertain to but I don't want other groups to have access to an secrets but their own.

@tduehr commented on GitHub (Jan 24, 2018): Implementing this would allow me to use Netbox to replace teampass along with IPAM and Racktables. I need to control access to secrets along side the devices they pertain to but I don't want other groups to have access to an secrets but their own.
Author
Owner

@i1caro commented on GitHub (Feb 7, 2018):

Hey @jeremystretch I have a rough implementation of this feature but would like your opinion and some direction.

  • I've added User to Tenants as a ManyToMany, this was the easiest for me but I think it should be the other way Tenants to User.
  • Added a filter_access function to ModelManagers
class ObjectFilterQuerySet(models.QuerySet):
    def build_args(self, user):
        return models.Q(tenant__users__in=[user])

    def filter_access(self, user):
        if not user.is_superuser:
            try:
                return self.filter(
                    self.build_args(user)
                )
            except TypeError:
                return self.none()
        return self
  • to Models:
    x Aggregate
    x Circuit
    x CircuitTermination
    x ConsolePort
    x ConsolePortTemplate
    x ConsoleServerPort
    x ConsoleServerPortTemplate
    x Device
    x DeviceBay
    x DeviceBayTemplate
    x DeviceRole
    x DeviceType
    x Interface
    x InterfaceConnection
    x InterfaceTemplate
    x InventoryItem
    x IPAddress
    x Platform
    x PowerOutlet
    x PowerOutletTemplate
    x PowerPort
    x PowerPortTemplate
    x Prefix
    x Provider
    x Rack
    x RackGroup
    x RackReservation
    x RackRole
    x Role
    x Service
    x Site
    x Tenant
    x TenantGroup
    x TopologyMap
    x VLAN
    x VLANGroup
    x VRF
    x ImageAttachment
  • The modules with no reference to User or Tenant I've added a Tenant field like DeviceType
  • Hide Tenant and TenantGroup from user in objects and selected the only available one in forms
  • Created a Middleware to extract the user as a global object connected to the request
  • Added filter_access to all get, post requests and general querysets that I could find
  • Added tenants to UserAdmin
@i1caro commented on GitHub (Feb 7, 2018): Hey @jeremystretch I have a rough implementation of this feature but would like your opinion and some direction. - I've added `User` to `Tenants` as a ManyToMany, this was the easiest for me but I think it should be the other way Tenants to User. - Added a `filter_access` function to ModelManagers ``` class ObjectFilterQuerySet(models.QuerySet): def build_args(self, user): return models.Q(tenant__users__in=[user]) def filter_access(self, user): if not user.is_superuser: try: return self.filter( self.build_args(user) ) except TypeError: return self.none() return self ``` - to Models: x Aggregate x Circuit x CircuitTermination x ConsolePort x ConsolePortTemplate x ConsoleServerPort x ConsoleServerPortTemplate x Device x DeviceBay x DeviceBayTemplate x DeviceRole x DeviceType x Interface x InterfaceConnection x InterfaceTemplate x InventoryItem x IPAddress x Platform x PowerOutlet x PowerOutletTemplate x PowerPort x PowerPortTemplate x Prefix x Provider x Rack x RackGroup x RackReservation x RackRole x Role x Service x Site x Tenant x TenantGroup x TopologyMap x VLAN x VLANGroup x VRF x ImageAttachment - The modules with no reference to User or Tenant I've added a Tenant field like DeviceType - Hide Tenant and TenantGroup from user in objects and selected the only available one in forms - Created a Middleware to extract the user as a global object connected to the request - Added `filter_access` to all `get`, `post` requests and general querysets that I could find - Added tenants to `UserAdmin`
Author
Owner

@puck commented on GitHub (Feb 19, 2018):

I'd find this useful as well!

@icaro with the more generic models (like DeviceType), have you made the Tenant a Many-to-Many? There are cases where I'd like to grant access at the TenantGroup level. Have you given any thought to that? In my case I have:

TenantGroup: Business Unit A
Tenants: Unit A Internal, Customer A.1, Customer A.2, Customer A.3
TenantGroup: Business Unit B
Tenants: Unit B Internal, Customer B.1, Customer B.2, Customer B.3

(My ideal would be to have Tenants nestable and ignore TenantGroups...)

@puck commented on GitHub (Feb 19, 2018): I'd find this useful as well! @icaro with the more generic models (like DeviceType), have you made the Tenant a Many-to-Many? There are cases where I'd like to grant access at the TenantGroup level. Have you given any thought to that? In my case I have: TenantGroup: Business Unit A Tenants: Unit A Internal, Customer A.1, Customer A.2, Customer A.3 TenantGroup: Business Unit B Tenants: Unit B Internal, Customer B.1, Customer B.2, Customer B.3 (My ideal would be to have Tenants nestable and ignore TenantGroups...)
Author
Owner

@orgito commented on GitHub (Aug 22, 2018):

Hello @i1caro , do you still working on that? That is a very critical feature. I'd love to see it implemented. Right now I'm forced to keep a legacy system for a few prefixes that I need to give write access for internal teams. Just waiting for this feature to finally get rid of the legacy system.

@orgito commented on GitHub (Aug 22, 2018): Hello @i1caro , do you still working on that? That is a very critical feature. I'd love to see it implemented. Right now I'm forced to keep a legacy system for a few prefixes that I need to give write access for internal teams. Just waiting for this feature to finally get rid of the legacy system.
Author
Owner

@0xf10e commented on GitHub (Jan 31, 2019):

@orgito @shugotek I'm afraid this feature would need some kind of sponsor. Something like a company paying someone to work on a mergable implementation.

@0xf10e commented on GitHub (Jan 31, 2019): @orgito @shugotek I'm afraid this feature would need some kind of sponsor. Something like a company paying someone to work on a mergable implementation.
Author
Owner

@jeremystretch commented on GitHub (Jan 31, 2019):

FYI I won't accept any sponsorship or merge any sponsored work on the project. Anyone who wishes to do so will need to maintain their own fork.

@jeremystretch commented on GitHub (Jan 31, 2019): FYI I won't accept any sponsorship or merge any sponsored work on the project. Anyone who wishes to do so will need to maintain their own fork.
Author
Owner

@0xf10e commented on GitHub (Feb 1, 2019):

I just meant if folks need this at work then maybe $WORK should have someone implementing this on company time.
And a company running this customer facing (sounds like @Schnatterowski's would) has more reason to maintain this code than one where it's just running internally for different departments ;)

@0xf10e commented on GitHub (Feb 1, 2019): I just meant if folks need this at work then maybe `$WORK` should have someone implementing this on company time. And a company running this customer facing (sounds like @Schnatterowski's would) has more reason to maintain this code than one where it's just running internally for different departments ;)
Author
Owner

@sdktr commented on GitHub (Feb 5, 2019):

FYI I won't accept any sponsorship or merge any sponsored work on the project. Anyone who wishes to do so will need to maintain their own fork.

Can you specify what a 'sponsorship' means in this context? When something is contributed in company time or when $company wants it's name/sponsorship included in some kind of 'wall-of-fame' somewhere?

@sdktr commented on GitHub (Feb 5, 2019): > FYI I won't accept any sponsorship or merge any sponsored work on the project. Anyone who wishes to do so will need to maintain their own fork. Can you specify what a 'sponsorship' means in this context? When something is contributed in company time or when $company wants it's name/sponsorship included in some kind of 'wall-of-fame' somewhere?
Author
Owner

@orgito commented on GitHub (Feb 5, 2019):

FYI I won't accept any sponsorship or merge any sponsored work on the project. Anyone who wishes to do so will need to maintain their own fork.

I'm curious, why not?

@orgito commented on GitHub (Feb 5, 2019): > FYI I won't accept any sponsorship or merge any sponsored work on the project. Anyone who wishes to do so will need to maintain their own fork. I'm curious, why not?
Author
Owner

@DanSheps commented on GitHub (Feb 5, 2019):

I would assume that sponsorship means it is still planned, eventually, but won't ever be included as a paid addition.

I can understand that, as it introduces a slippery slope of "if I throw enough money at jstretch, he will do whatever I ask to Netbox right?"

This still has the "Accepted" and "Major Feature" tags, so unless those change, you can assume this is still a planned feature at some point.

@DanSheps commented on GitHub (Feb 5, 2019): I would assume that sponsorship means it is still planned, eventually, but won't ever be included as a paid addition. I can understand that, as it introduces a slippery slope of "if I throw enough money at jstretch, he will do whatever I ask to Netbox right?" This still has the "Accepted" and "Major Feature" tags, so unless those change, you can assume this is still a planned feature at some point.
Author
Owner

@jeremystretch commented on GitHub (Feb 6, 2019):

I can understand that, as it introduces a slippery slope of "if I throw enough money at jstretch, he will do whatever I ask to Netbox right?"

Bingo. The maintainers only have so much time we can contribute to NetBox's development. Imagine if we had to choose between implementing a feature 80% of the user base wants, for free, or implementing some niche feature which would highly benefit one specific vendor, in exchange for $10K? I'm not even going to pretend: I'd take the $10K in a heartbeat. A ban on paid work prevents vendors from steering the direction of the product in their interest.

@jeremystretch commented on GitHub (Feb 6, 2019): > I can understand that, as it introduces a slippery slope of "if I throw enough money at jstretch, he will do whatever I ask to Netbox right?" Bingo. The maintainers only have so much time we can contribute to NetBox's development. Imagine if we had to choose between implementing a feature 80% of the user base wants, for free, or implementing some niche feature which would highly benefit one specific vendor, in exchange for $10K? I'm not even going to pretend: I'd take the $10K in a heartbeat. A ban on paid work prevents vendors from steering the direction of the product in their interest.
Author
Owner

@sdktr commented on GitHub (Feb 7, 2019):

I can understand that, as it introduces a slippery slope of "if I throw enough money at jstretch, he will do whatever I ask to Netbox right?"

Bingo. The maintainers only have so much time we can contribute to NetBox's development. Imagine if we had to choose between implementing a feature 80% of the user base wants, for free, or implementing some niche feature which would highly benefit one specific vendor, in exchange for $10K? I'm not even going to pretend: I'd take the $10K in a heartbeat. A ban on paid work prevents vendors from steering the direction of the product in their interest.

If understood correctly a code contribution made during work hours ('paid for by the boss') doesn't count as a sponsorship then, right?

@sdktr commented on GitHub (Feb 7, 2019): > > I can understand that, as it introduces a slippery slope of "if I throw enough money at jstretch, he will do whatever I ask to Netbox right?" > > Bingo. The maintainers only have so much time we can contribute to NetBox's development. Imagine if we had to choose between implementing a feature 80% of the user base wants, for free, or implementing some niche feature which would highly benefit one specific vendor, in exchange for $10K? I'm not even going to pretend: I'd take the $10K in a heartbeat. A ban on paid work prevents vendors from steering the direction of the product in their interest. If understood correctly a code contribution made during work hours ('paid for by the boss') doesn't count as a sponsorship then, right?
Author
Owner

@Tesshu commented on GitHub (May 28, 2019):

Hello @i1caro , have you done any progress so far? We are evaluating Netbox for device inventory in my company and this is something that would be very useful for us in order to correctly segregate the permissions between our teams.

@Tesshu commented on GitHub (May 28, 2019): Hello @i1caro , have you done any progress so far? We are evaluating Netbox for device inventory in my company and this is something that would be very useful for us in order to correctly segregate the permissions between our teams.
Author
Owner

@DanSheps commented on GitHub (May 28, 2019):

@Tesshu Please don't ask for updates here.

Feature requests will get done when they get done.

@DanSheps commented on GitHub (May 28, 2019): @Tesshu Please don't ask for updates here. Feature requests will get done when they get done.
Author
Owner

@jeremystretch commented on GitHub (Jan 30, 2020):

Keep comments on-topic for the requested feature, please.

@jeremystretch commented on GitHub (Jan 30, 2020): Keep comments on-topic for the requested feature, please.
Author
Owner

@tb-killa commented on GitHub (Jan 30, 2020):

How about build the needing permissions system using https://github.com/django-guardian/django-guardian, which handles per object permissions?

@tb-killa commented on GitHub (Jan 30, 2020): How about build the needing permissions system using https://github.com/django-guardian/django-guardian, which handles per object permissions?
Author
Owner

@jeremystretch commented on GitHub (Jan 30, 2020):

django-guardian, while certainly a nice project, would not meet our requirements for scale. It manages permissions using explicit representations in the database, mapping users to individual objects.

Given the complexity of models in NetBox, we need to devise a solution for evaluating permissions dynamically based on object attributes. For example, to say "grant change access for all sites assigned to tenant X" or for "all prefixes within 10.0.0.0/8" without creating and maintaining a separate rule for each of the applicable objects.

@jeremystretch commented on GitHub (Jan 30, 2020): django-guardian, while certainly a nice project, would not meet our requirements for scale. It manages permissions using explicit representations in the database, mapping users to individual objects. Given the complexity of models in NetBox, we need to devise a solution for evaluating permissions dynamically based on object attributes. For example, to say "grant change access for all sites assigned to tenant X" or for "all prefixes within 10.0.0.0/8" without creating and maintaining a separate rule for each of the applicable objects.
Author
Owner

@tb-killa commented on GitHub (Jan 30, 2020):

django-guardian, while certainly a nice project, would not meet our requirements for scale. It manages permissions using explicit representations in the database, mapping users to individual objects.

Given the complexity of models in NetBox, we need to devise a solution for evaluating permissions dynamically based on object attributes. For example, to say "grant change access for all sites assigned to tenant X" or for "all prefixes within 10.0.0.0/8" without creating and maintaining a separate rule for each of the applicable objects.

With this we need something like Tags-like-permission model where tag to tenant means access control.
Maybe I don't know if this sound stupid but what about create automatic tag who named like the generated tenant and who could be assign to every tagable object. For this we only need one simple middleware logic for permission filter. If my tenant named Carl it create tag Carl too. This allows us to bring Carl permissions to everything where he is as tag placed too.

@tb-killa commented on GitHub (Jan 30, 2020): > django-guardian, while certainly a nice project, would not meet our requirements for scale. It manages permissions using explicit representations in the database, mapping users to individual objects. > > Given the complexity of models in NetBox, we need to devise a solution for evaluating permissions dynamically based on object attributes. For example, to say "grant change access for all sites assigned to tenant X" or for "all prefixes within 10.0.0.0/8" without creating and maintaining a separate rule for each of the applicable objects. With this we need something like Tags-like-permission model where tag to tenant means access control. Maybe I don't know if this sound stupid but what about create automatic tag who named like the generated tenant and who could be assign to every tagable object. For this we only need one simple middleware logic for permission filter. If my tenant named Carl it create tag Carl too. This allows us to bring Carl permissions to everything where he is as tag placed too.
Author
Owner

@DaveBaughn commented on GitHub (Jan 30, 2020):

@tb-killa you stole my thunder. I was just writing this up when you posted. :)

@DaveBaughn commented on GitHub (Jan 30, 2020): @tb-killa you stole my thunder. I was just writing this up when you posted. :)
Author
Owner

@dd1245 commented on GitHub (Mar 2, 2020):

Rather than deep object level controls that some has asked for such as specific sites, is it possible to limit users access to anything within there tenant? If we could have users linked with a single tenant or tenant group and just view object inside there tenancy that would be more than enough and can't imagine a need to get more granular than that.

@dd1245 commented on GitHub (Mar 2, 2020): Rather than deep object level controls that some has asked for such as specific sites, is it possible to limit users access to anything within there tenant? If we could have users linked with a single tenant or tenant group and just view object inside there tenancy that would be more than enough and can't imagine a need to get more granular than that.
Author
Owner

@nonsbe commented on GitHub (Mar 4, 2020):

For us the same would apply if a tenant based view/edit permission is available. We have a lot of on-premise installations that we are servicing. In some cases it would be helpful that the documentation for the customer IT dept is available via netbox.

Rather than deep object level controls that some has asked for such as specific sites, is it possible to limit users access to anything within there tenant? If we could have users linked with a single tenant or tenant group and just view object inside there tenancy that would be more than enough and can't imagine a need to get more granular than that.

@nonsbe commented on GitHub (Mar 4, 2020): For us the same would apply if a tenant based view/edit permission is available. We have a lot of on-premise installations that we are servicing. In some cases it would be helpful that the documentation for the customer IT dept is available via netbox. > Rather than deep object level controls that some has asked for such as specific sites, is it possible to limit users access to anything within there tenant? If we could have users linked with a single tenant or tenant group and just view object inside there tenancy that would be more than enough and can't imagine a need to get more granular than that.
Author
Owner

@dstarner commented on GitHub (Mar 5, 2020):

Hmmm, @jeremystretch, I've played with nested RBAC permissions in projects before. I know 2.8 is already scoped out, but I can try to put together a design doc or PR to scope it for 2.9 - or even a 3.0 😅. I designed my previous permissions loosely off of Ansible AWX's model, where there is a Role model that maps to an instance in the DB and model accessors that control if a user has a certain Role assigned. Then you can easily do multi-level permissioning and per tenant / resource permissioning. Its a big goal, but I feel like this is a needed step for the project.

Feel free to reach out if you want to chat about it. I know its a bit bigger of a feature, but I don't mind working on it because it would add a ton of business value to myself (and I presume others in this issue)

@dstarner commented on GitHub (Mar 5, 2020): Hmmm, @jeremystretch, I've played with nested RBAC permissions in projects before. I know 2.8 is already scoped out, but I can try to put together a design doc or PR to scope it for 2.9 - or even a 3.0 😅. I designed my previous permissions loosely off of Ansible AWX's model, where there is a [`Role` model](https://github.com/ansible/awx/blob/devel/awx/main/models/rbac.py#L131) that maps to an instance in the DB and [model accessors](https://github.com/ansible/awx/blob/devel/awx/main/access.py#L178) that control if a user has a certain Role assigned. Then you can easily do multi-level permissioning and per tenant / resource permissioning. Its a big goal, but I feel like this is a needed step for the project. Feel free to reach out if you want to chat about it. I know its a bit bigger of a feature, but I don't mind working on it because it would add a ton of business value to myself (and I presume others in this issue)
Author
Owner

@dstarner commented on GitHub (Mar 5, 2020):

The major resources that I could see be role'd around would be:

  • Tenants: You are in the tenancy, you can see everything it owns
  • Sites: If you have access to the site, you can see anything in that site
  • Device/Rack & VMs/Clusters: if you have access to a rack or device (or VM or cluster), you see all associate devices, components

I'm probably missing some things, but they can be worked in as decided

@dstarner commented on GitHub (Mar 5, 2020): The major resources that I could see be role'd around would be: * Tenants: You are in the tenancy, you can see everything it owns * Sites: If you have access to the site, you can see anything in that site * Device/Rack & VMs/Clusters: if you have access to a rack or device (or VM or cluster), you see all associate devices, components I'm probably missing some things, but they can be worked in as decided
Author
Owner

@sossie07 commented on GitHub (Apr 1, 2020):

I think this would be useful too, we would like to permit our clients to view and or edit their own tenancy.

Keep up the good work team, looking forward to 2.9!

@sossie07 commented on GitHub (Apr 1, 2020): I think this would be useful too, we would like to permit our clients to view and or edit their own tenancy. Keep up the good work team, looking forward to 2.9!
Author
Owner

@WillIrvine commented on GitHub (Apr 23, 2020):

Because of the way Netbox stores custom fields as separate objects in the database, does this mean we may be able to restrict which custom fields will be able to be displayed to certain groups aswell?

I understand this may not be possible with the native fields attached to the objects if we are looking at per object permissions.

@WillIrvine commented on GitHub (Apr 23, 2020): Because of the way Netbox stores custom fields as separate objects in the database, does this mean we may be able to restrict which custom fields will be able to be displayed to certain groups aswell? I understand this may not be possible with the native fields attached to the objects if we are looking at per object permissions.
Author
Owner

@jeremystretch commented on GitHub (May 22, 2020):

I've made a lot of progress on this feature recently, visible in the 554-object-permissions branch. Although much work remains, I'm happy to report that the core functionality appears to be working very well.

At a high level, object-based permission assignment is done by crafting one or more ObjectPermission objects mapping users and/or groups to object types. Each instance has four boolean fields indicating the type(s) of action it permits; some set of view, add, change, and delete. Each instance also has a JSON representation of Django query filters, and therein lies the magic.

Say, for example, you want to allow a user to view and edit only devices assigned to a specific tenant. You would assign the ObjectPermission to the Device model and enter the following attribute to identify the tenant by slug:

{
    "tenant__slug": "acme-inc",
}

Maybe we want to further restrict the user to only devices of particular roles:

{
    "tenant__slug": "acme-inc",
    "device_role__slug__in": ["testing", "lab", "etc"],
}

As you can see, this approach is very flexible. All attributes within a single ObjectPermission are combined using a logical AND. To OR multiple attributes, simply create multiple ObjectPermissions.

These parameters are enforced whenever a user retrieves objects from NetBox. For example, when viewing the devices list, the user will only see devices matching the specified parameters. These parameters are similarly applied when modifying or deleting an object: the action will be permitted only if the resulting object matches the specified parameters.

The evaluation of object-level permissions is deferential to the model-level permissions currently provided by NetBox (and built-into the Django framework). That is to say, if a user has the dcim.view_device model-level permission assigned, all devices will be visible to that user. Otherwise, the user will be able to view only devices matching the prescribed ObjectPermission attributes assigned to that user, if any.

@jeremystretch commented on GitHub (May 22, 2020): I've made a lot of progress on this feature recently, visible in the `554-object-permissions` branch. Although much work remains, I'm happy to report that the core functionality appears to be working very well. At a high level, object-based permission assignment is done by crafting one or more ObjectPermission objects mapping users and/or groups to object types. Each instance has four boolean fields indicating the type(s) of action it permits; some set of view, add, change, and delete. Each instance also has a JSON representation of [Django query filters](https://docs.djangoproject.com/en/3.0/topics/db/queries/#retrieving-specific-objects-with-filters), and therein lies the magic. Say, for example, you want to allow a user to view and edit only devices assigned to a specific tenant. You would assign the ObjectPermission to the Device model and enter the following attribute to identify the tenant by slug: ``` { "tenant__slug": "acme-inc", } ``` Maybe we want to further restrict the user to only devices of particular roles: ``` { "tenant__slug": "acme-inc", "device_role__slug__in": ["testing", "lab", "etc"], } ``` As you can see, this approach is _very_ flexible. All attributes within a single ObjectPermission are combined using a logical AND. To OR multiple attributes, simply create multiple ObjectPermissions. These parameters are enforced whenever a user retrieves objects from NetBox. For example, when viewing the devices list, the user will only see devices matching the specified parameters. These parameters are similarly applied when modifying or deleting an object: the action will be permitted _only_ if the resulting object matches the specified parameters. The evaluation of object-level permissions is deferential to the model-level permissions currently provided by NetBox (and built-into the Django framework). That is to say, if a user has the `dcim.view_device` model-level permission assigned, _all_ devices will be visible to that user. Otherwise, the user will be able to view only devices matching the prescribed ObjectPermission attributes assigned to that user, if any.
Author
Owner

@sdktr commented on GitHub (May 23, 2020):

Awesome work!

At a high level, object-based permission assignment is done by crafting one or more ObjectPermission objects mapping users and/or groups to object types.

Would these 'ObjectPermission' objects only be mapped 1:1 to Users/Groups, or could this be implemented on some sort of session level ObjectPermission as well?

The scenario I'm thinking about is more of a view filter then strict user based permission.
For example (assuming tenant assignment as the only filter configured): with multiple tenants using the same system, an overall administrator could have a seperate ObjectPermission for each tenant in the system. In the GUI this list of ObjectPermissions could manifest as a dropdown -list for this users session. Selecting a permission ('context') would result in a 1:1 view of what the respective tenant could see. The results for each of the views would be filtered accordingly without specifying the filters setup in the ObjectPermission by hand on each click etc.

^^^ the above might be solvable with a plugin that changes a user's mapping to the ObjectPermission as well? Or dynamic group membership even, but I think this requires a re-logon.

Curious to your thoughts about this scenario.

@sdktr commented on GitHub (May 23, 2020): Awesome work! > At a high level, object-based permission assignment is done by crafting one or more ObjectPermission objects mapping users and/or groups to object types. Would these 'ObjectPermission' objects only be mapped 1:1 to Users/Groups, or _could_ this be implemented on some sort of _session_ level ObjectPermission as well? The scenario I'm thinking about is more of a view filter then strict user based permission. For example (assuming tenant assignment as the only filter configured): with multiple tenants using the same system, an overall administrator could have a seperate ObjectPermission for each tenant in the system. In the GUI this list of ObjectPermissions could manifest as a dropdown -list for this users session. Selecting a permission ('context') would result in a 1:1 view of what the respective tenant could see. The results for each of the views would be filtered accordingly without specifying the filters setup in the ObjectPermission by hand on each click etc. ^^^ the above might be solvable with a plugin that changes a user's mapping to the ObjectPermission as well? Or dynamic group membership even, but I think this requires a re-logon. Curious to your thoughts about this scenario.
Author
Owner

@jeremystretch commented on GitHub (May 23, 2020):

All permissions are assigned based on users and groups. There is no mechanism to support what you describe.

@jeremystretch commented on GitHub (May 23, 2020): All permissions are assigned based on users and groups. There is no mechanism to support what you describe.
Author
Owner

@edylam commented on GitHub (May 24, 2020):

All permissions are assigned based on users and groups. There is no mechanism to support what you describe.

Awesome work!
I've tried the 554-object-permissions branch accroding to your example, but I got the error as below:

Request Method: POST
http://test.com:8099/admin/users/objectpermission/add/
3.0.6
TypeError
filter() argument after ** must be a mapping, not NoneType
/home/netbox554/netbox/users/models.py in clean, line 269
/usr/bin/python3
3.7.5
['/home/netbox554/netbox', '/usr/lib/python37.zip', '/usr/lib/python3.7', '/usr/lib/python3.7/lib-dynload', '/usr/local/lib/python3.7/dist-packages', '/usr/lib/python3/dist-packages']
............
Any suggest?

1

@edylam commented on GitHub (May 24, 2020): > All permissions are assigned based on users and groups. There is no mechanism to support what you describe. Awesome work! I've tried the 554-object-permissions branch accroding to your example, but I got the error as below: Request Method: | POST -- | -- http://test.com:8099/admin/users/objectpermission/add/ 3.0.6 TypeError filter() argument after ** must be a mapping, not NoneType /home/netbox554/netbox/users/models.py in clean, line 269 /usr/bin/python3 3.7.5 ['/home/netbox554/netbox', '/usr/lib/python37.zip', '/usr/lib/python3.7', '/usr/lib/python3.7/lib-dynload', '/usr/local/lib/python3.7/dist-packages', '/usr/lib/python3/dist-packages'] ............ Any suggest? ![1](https://user-images.githubusercontent.com/524327/82744427-03fb7100-9dab-11ea-871a-7e14d9b8acb5.png)
Author
Owner

@edylam commented on GitHub (May 24, 2020):

All permissions are assigned based on users and groups. There is no mechanism to support what you describe.

Awesome work!
I've tried the 554-object-permissions branch accroding to your example, but I got the error as below:

I've deleted the "," in line 2, just like
{
"tenant__slug": "test-aa"
}
then the POST success.

@edylam commented on GitHub (May 24, 2020): > > All permissions are assigned based on users and groups. There is no mechanism to support what you describe. > > Awesome work! > I've tried the 554-object-permissions branch accroding to your example, but I got the error as below: I've deleted the "," in line 2, just like { "tenant__slug": "test-aa" } then the POST success.
Author
Owner

@edylam commented on GitHub (May 24, 2020):

In utility views, the object permission working well, but in home view as follow

    stats = {

        # Organization
        'site_count': Site.objects.count(),
        'tenant_count': Tenant.objects.count(),

        # DCIM
        'rack_count': Rack.objects.count(),
        'devicetype_count': DeviceType.objects.count(),
        'device_count': Device.objects.count(),
        'interface_connections_count': connected_interfaces.count(),
        'cable_count': cables.count(),
        'console_connections_count': connected_consoleports.count(),
        'power_connections_count': connected_powerports.count(),
        'powerpanel_count': PowerPanel.objects.count(),
        'powerfeed_count': PowerFeed.objects.count(),

the counts are still the counts of all the objects of a model. So, firstly, in home view, it should get all the counts of objects of models which the user has permission to view. change and so on, is it right?

@edylam commented on GitHub (May 24, 2020): In utility views, the object permission working well, but in home view as follow stats = { # Organization 'site_count': Site.objects.count(), 'tenant_count': Tenant.objects.count(), # DCIM 'rack_count': Rack.objects.count(), 'devicetype_count': DeviceType.objects.count(), 'device_count': Device.objects.count(), 'interface_connections_count': connected_interfaces.count(), 'cable_count': cables.count(), 'console_connections_count': connected_consoleports.count(), 'power_connections_count': connected_powerports.count(), 'powerpanel_count': PowerPanel.objects.count(), 'powerfeed_count': PowerFeed.objects.count(), the counts are still the counts of all the objects of a model. So, firstly, in home view, it should get all the counts of objects of models which the user has permission to view. change and so on, is it right?
Author
Owner

@jeremystretch commented on GitHub (May 26, 2020):

This is a work in progress. It is currently broken in many places. Tomorrow, it will be broken in different places. Please do not attempt to troubleshoot.

@jeremystretch commented on GitHub (May 26, 2020): This is a work in progress. It is currently broken in many places. Tomorrow, it will be broken in different places. Please do not attempt to troubleshoot.
Author
Owner

@hsluoyz commented on GitHub (May 26, 2020):

Have we tried PyCasbin? It supports RBAC with tenant model, which I think fulfilled our goal well.

https://casbin.org/

@hsluoyz commented on GitHub (May 26, 2020): Have we tried PyCasbin? It supports RBAC with tenant model, which I think fulfilled our goal well. https://casbin.org/
Author
Owner

@edylam commented on GitHub (May 27, 2020):

This is a work in progress. It is currently broken in many places. Tomorrow, it will be broken in different places. Please do not attempt to troubleshoot.

OK

@edylam commented on GitHub (May 27, 2020): > This is a work in progress. It is currently broken in many places. Tomorrow, it will be broken in different places. Please do not attempt to troubleshoot. OK
Author
Owner

@jeremystretch commented on GitHub (Jun 3, 2020):

This has been implemented in PR #4705. There is almost certain to be further testing and mild development to be done, however the functionality is in place.

@jeremystretch commented on GitHub (Jun 3, 2020): This has been implemented in PR #4705. There is almost certain to be further testing and mild development to be done, however the functionality is in place.
Author
Owner

@jeremystretch commented on GitHub (Jul 23, 2020):

FYI v2.9-beta1 has been released. The community's support in testing this implementation will be crucial to its success. https://github.com/netbox-community/netbox/releases/tag/v2.9-beta1

@jeremystretch commented on GitHub (Jul 23, 2020): FYI v2.9-beta1 has been released. The community's support in testing this implementation will be crucial to its success. https://github.com/netbox-community/netbox/releases/tag/v2.9-beta1
Author
Owner

@bluikko commented on GitHub (Jul 24, 2020):

Excellent, this feature could make my life much easier.
I need to ask: does this work together with LDAP authentication - more specifically LDAP groups?

@bluikko commented on GitHub (Jul 24, 2020): Excellent, this feature could make my life much easier. I need to ask: does this work together with LDAP authentication - more specifically LDAP groups?
Author
Owner

@LeSnowTiger commented on GitHub (Jul 24, 2020):

Excellent, this feature could make my life much easier.
I need to ask: does this work together with LDAP authentication - more specifically LDAP groups?

I'm testing it currently and as far as I see it works with LDAP groups.

@LeSnowTiger commented on GitHub (Jul 24, 2020): > Excellent, this feature could make my life much easier. > I need to ask: does this work together with LDAP authentication - more specifically LDAP groups? I'm testing it currently and as far as I see it works with LDAP groups.
Author
Owner

@jeremystretch commented on GitHub (Jul 24, 2020):

It helps to recognize authentication and authorization as two discrete aspects: The former entails proving the identify of a user and the groups to which he or she belongs, whereas the later determines what actions the user is permitted to take. As far as NetBox is concerned, LDAP handles the first part, authenticating a user's credentials and (optionally) assigning the user to one or more groups. NetBox's permissions system handles the mapping of users and groups to permitted actions. So, as long as the authentication system is successful in assigning a user to a group, the permissions system can take it from there.

@jeremystretch commented on GitHub (Jul 24, 2020): It helps to recognize _authentication_ and _authorization_ as two discrete aspects: The former entails proving the identify of a user and the groups to which he or she belongs, whereas the later determines what actions the user is permitted to take. As far as NetBox is concerned, LDAP handles the first part, authenticating a user's credentials and (optionally) assigning the user to one or more groups. NetBox's permissions system handles the mapping of users and groups to permitted actions. So, as long as the authentication system is successful in assigning a user to a group, the permissions system can take it from there.
Author
Owner

@bluikko commented on GitHub (Jul 24, 2020):

Agreed. I am not familiar at all with the Django authentication/authorization system - which seems to be not clearly separated (at least in NetBox). For example the configuration only talks about authentication. I wish there would be clear separate authentication and authorization so that I could authenticate with RemoteUserBackend and still use django-auth-ldap for authorization (which specifically says that group mirroring does not work together with RemoteUserBackend - but still talks about authorizing together with RemoteUserBackend). Will need to do more reading and testing...

This is now strongly straying to the "support" direction which I tried to avoid.

@bluikko commented on GitHub (Jul 24, 2020): Agreed. I am not familiar at all with the Django authentication/authorization system - which seems to be not clearly separated (at least in NetBox). For example the configuration only talks about authentication. I wish there would be clear separate authentication and authorization so that I could authenticate with RemoteUserBackend and still use django-auth-ldap for authorization (which specifically says that group mirroring does not work together with RemoteUserBackend - but still talks about authorizing together with RemoteUserBackend). Will need to do more reading and testing... This is now strongly straying to the "support" direction which I tried to avoid.
Author
Owner

@LeSnowTiger commented on GitHub (Jul 27, 2020):

FYI v2.9-beta1 has been released. The community's support in testing this implementation will be crucial to its success. https://github.com/netbox-community/netbox/releases/tag/v2.9-beta1

I played around with it the last few days. Overall its really solid.
But I see some flaws regarding devices and components.

For example:
I limit the permission to a device with {"tenant__slug": "test-tenant"}.
Now I only see the devices and no components (for example interfaces). So I have to add "view interface, dcim" permissions.
But now I can see every interface which was created in Netbox. Since its currently not possible to set a tenant on the interface I would have to work with additional permissions and tags.

I think (if possible) view permissions on components should be inherited from the parent device. So if I have permissions to device A and device B I only should see the components from these devices.

@LeSnowTiger commented on GitHub (Jul 27, 2020): > FYI v2.9-beta1 has been released. The community's support in testing this implementation will be crucial to its success. https://github.com/netbox-community/netbox/releases/tag/v2.9-beta1 I played around with it the last few days. Overall its really solid. But I see some flaws regarding devices and components. For example: I limit the permission to a device with `{"tenant__slug": "test-tenant"}`. Now I only see the devices and no components (for example interfaces). So I have to add "view interface, dcim" permissions. But now I can see every interface which was created in Netbox. Since its currently not possible to set a tenant on the interface I would have to work with additional permissions and tags. I think (if possible) view permissions on components should be inherited from the parent device. So if I have permissions to device A and device B I only should see the components from these devices.
Author
Owner

@jeremystretch commented on GitHub (Jul 27, 2020):

Since its currently not possible to set a tenant on the interface I would have to work with additional permissions and tags.

You can create a new permission for all device component types with the constraint {"device__tenant__slug": "test-tenant"}.

@jeremystretch commented on GitHub (Jul 27, 2020): > Since its currently not possible to set a tenant on the interface I would have to work with additional permissions and tags. You can create a new permission for all device component types with the constraint `{"device__tenant__slug": "test-tenant"}`.
Author
Owner

@LeSnowTiger commented on GitHub (Jul 27, 2020):

Thank you! That seems to do exactly what I wanted. I´ve said nothing!

@LeSnowTiger commented on GitHub (Jul 27, 2020): Thank you! That seems to do exactly what I wanted. I´ve said nothing!
Author
Owner

@jeremystretch commented on GitHub (Jul 27, 2020):

I mean, it's a valid point: shouldn't permitted devices "automatically" include child objects? There are two issues with the potential implementation though:

  1. It would require nonstandard application of permissions for certain types of objects (i.e. device components), which is best avoided wherever possible
  2. It's easy to imagine a use case where you don't want to automatically show all device components. For example, maybe you have on-site contractors that need access to power and console connections, but should not be able to see interfaces or device bays.
@jeremystretch commented on GitHub (Jul 27, 2020): I mean, it's a valid point: shouldn't permitted devices "automatically" include child objects? There are two issues with the potential implementation though: 1. It would require nonstandard application of permissions for certain types of objects (i.e. device components), which is best avoided wherever possible 2. It's easy to imagine a use case where you _don't_ want to automatically show all device components. For example, maybe you have on-site contractors that need access to power and console connections, but should not be able to see interfaces or device bays.
Author
Owner

@LeSnowTiger commented on GitHub (Jul 27, 2020):

It would be really cool to have something like this. But since i really have no clue about how django works I can`t say more than this. Your provided solution works fine for me.

Another thing I came across. While playing around with the "Create" permission I noticed that there is no easy way to limit an user to only create addresses in a specific prefix. (Maybe I`m missing something again)

The solution I came up with is with regex:
{"address__regex": "192.1.8[0-9].*/24"}, which would let the user only create addresses from 192.168.80.1/24 - 192.168.89.254/24. But I think it does not really scale well.

Anyway, thank you very much for this feature!

@LeSnowTiger commented on GitHub (Jul 27, 2020): It would be really cool to have something like this. But since i really have no clue about how django works I can`t say more than this. Your provided solution works fine for me. Another thing I came across. While playing around with the "Create" permission I noticed that there is no easy way to limit an user to only create addresses in a specific prefix. (Maybe I`m missing something again) The solution I came up with is with regex: `{"address__regex": "192.1.8[0-9].*/24"}`, which would let the user only create addresses from 192.168.80.1/24 - 192.168.89.254/24. But I think it does not really scale well. Anyway, thank you very much for this feature!
Author
Owner

@jeremystretch commented on GitHub (Jul 27, 2020):

Honestly I'm just behind on the documentation for this (said every developer ever, right?). Prefixes and IP addresses do have filters available for stuff like this, but you have to dig a bit to find them. For example, if you want to restrict IP addresses to 192.168.0.0/24:

{"address__net_contained": "192.168.0.0/24"}

The net_contained filter matches any addresses contained by the specified network. For now, you can find the complete list of available filters here; hopefully we can get some proper documentation in place soon.

@jeremystretch commented on GitHub (Jul 27, 2020): Honestly I'm just behind on the documentation for this (said every developer ever, right?). Prefixes and IP addresses do have filters available for stuff like this, but you have to dig a bit to find them. For example, if you want to restrict IP addresses to 192.168.0.0/24: ``` {"address__net_contained": "192.168.0.0/24"} ``` The `net_contained` filter matches any addresses _contained by_ the specified network. For now, you can find the complete list of available filters [here](https://github.com/netbox-community/netbox/blob/develop/netbox/ipam/lookups.py); hopefully we can get some proper documentation in place soon.
Author
Owner

@LeSnowTiger commented on GitHub (Jul 27, 2020):

That looks good! I'll have a look tomorrow. And no problem, your documentation is on the better end of documentations. 😉

@LeSnowTiger commented on GitHub (Jul 27, 2020): That looks good! I'll have a look tomorrow. And no problem, your documentation is on the better end of documentations. 😉
Author
Owner

@cpmills1975 commented on GitHub (Jul 28, 2020):

Thanks for this! This is a huge step forward and waiting on this has been a blocker to me merging two NetBox instances in to one. I've played briefly with a dev instance I've just thrown up (docker using develop-2.9 so forgive me if I'm some commits behind).

There are a couple of things that strike me as potentially painful or a bit odd. Firstly, the method of granting permissions - one assumes the majority use case for this will be to limit tenants to their own 'stuff'. I started out creating three devices, one of which was assigned tenant 'tenant-A'. I created a group for 'tenant-a' and added user 'bob'. I then granted filtered permissions for devices to tenant-a using {"tenant__slug": "tenant-a"}. So far so good. I then came across the challenges noted above for components of the devices so added additional permissions for {"device__tenant__slug": "tenant-a"} on things like interfaces, console ports, power ports, etc. It occurred to me at this point that while this is awesomely powerful, it would be painful to scale. When I inevitably have to create a tenant-B, I already need to replicate and modify two rules for group 'tenant-b' etc. Would it be possible to consider applying permissions through the group create/edit page or provide a means to duplicate permissions or duplicate a group with all existing permissions that I can then tweak for the amended slugs? Would permissions be able to be OR'd? i.e. be able to specify [{"tenant__slug": "tenant-a"}, {"device__tenant__slug": "tenant-a}] and when applied to have NetBox ignore those rules which make no sense - I see it currently throws an error for filters that don't exist for a given object type hence the need for multiple distinct rules.

Secondly, having granted read permissions to all racks, 'bob' can now see details for devices they have no permissions over. I get that showing empty rack would be misleading, but could perhaps devices be rendered in a similar way to rack reservations to indicate that the space is used, but permissions don't grant access to know what by?

As a result of this, clicking on a device in the rack elevation view that I don't have permissions over results in a 404 error. Would a 403 forbidden be possible? Even better, could that redirect to the login page to make it clear permissions are not granted? Of course, if the devices/rack units were not clickable, the problem would go away anyway.

@cpmills1975 commented on GitHub (Jul 28, 2020): Thanks for this! This is a huge step forward and waiting on this has been a blocker to me merging two NetBox instances in to one. I've played briefly with a dev instance I've just thrown up (docker using develop-2.9 so forgive me if I'm some commits behind). There are a couple of things that strike me as potentially painful or a bit odd. Firstly, the method of granting permissions - one assumes the majority use case for this will be to limit tenants to their own 'stuff'. I started out creating three devices, one of which was assigned tenant 'tenant-A'. I created a group for 'tenant-a' and added user 'bob'. I then granted filtered permissions for devices to tenant-a using {"tenant__slug": "tenant-a"}. So far so good. I then came across the challenges noted above for components of the devices so added additional permissions for {"device__tenant__slug": "tenant-a"} on things like interfaces, console ports, power ports, etc. It occurred to me at this point that while this is awesomely powerful, it would be painful to scale. When I inevitably have to create a tenant-B, I already need to replicate and modify two rules for group 'tenant-b' etc. Would it be possible to consider applying permissions through the group create/edit page or provide a means to duplicate permissions or duplicate a group with all existing permissions that I can then tweak for the amended slugs? Would permissions be able to be OR'd? i.e. be able to specify [{"tenant__slug": "tenant-a"}, {"device__tenant__slug": "tenant-a}] and when applied to have NetBox ignore those rules which make no sense - I see it currently throws an error for filters that don't exist for a given object type hence the need for multiple distinct rules. Secondly, having granted read permissions to all racks, 'bob' can now see details for devices they have no permissions over. I get that showing empty rack would be misleading, but could perhaps devices be rendered in a similar way to rack reservations to indicate that the space is used, but permissions don't grant access to know what by? As a result of this, clicking on a device in the rack elevation view that I don't have permissions over results in a 404 error. Would a 403 forbidden be possible? Even better, could that redirect to the login page to make it clear permissions are not granted? Of course, if the devices/rack units were not clickable, the problem would go away anyway.
Author
Owner

@jeremystretch commented on GitHub (Jul 28, 2020):

@cpmills1975 thanks for the feedback, but there's a lot to unpack in your comments above. Please consider opening separate issues for each topic so that we can have a more organized conversation around each.

@jeremystretch commented on GitHub (Jul 28, 2020): @cpmills1975 thanks for the feedback, but there's a lot to unpack in your comments above. Please consider opening separate issues for each topic so that we can have a more organized conversation around each.
Author
Owner

@jeremystretch commented on GitHub (Jul 28, 2020):

In fact, I'm going to lock the conversation here just to ensure that additional new commentary doesn't get lost at the bottom of a four-year-old issue. For any bugs or improvements concerning this feature in the v2.9 beta, please open a new issue. Thanks!

@jeremystretch commented on GitHub (Jul 28, 2020): In fact, I'm going to lock the conversation here just to ensure that additional new commentary doesn't get lost at the bottom of a four-year-old issue. For any bugs or improvements concerning this feature in the v2.9 beta, please open a new issue. Thanks!
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: starred/netbox#432