Granting varied levels of NAS permission based on LDAP group membership
FR users & maintainers, I have a functional FreeRADIUS installation (3.0.20 - I know it's slightly old, it's from the Ubuntu repo - on Ubuntu 20.04LTS), being used for management access and authorization on varied network equipment at an ISP (not everything supports TACACS so we're using RADIUS). When I first setup this system, Alan and other members of this group gave me a bunch of help and I threw together a policy.d file (called "group_authz") that is querying for LDAP group membership, and then altering the replies back to the various NASes with specific attributes to grant read-only or read-write permissions on the devices. I was originally told by my boss that a universal read-only and read-write group would be sufficient, so it was pretty easy to check for membership in one of those two groups and then just send back specific attributes depending on %{client:nas_type}. Of course, now the goalposts have shifted. I've been told that we need "tiered" access. Some users (tier 2 NOC team) should get R/W to most of the network, but should *not* have any access at all to some of the more sensitive gear (core/datacenter network stuff). Others (tier 1 NOC team) should get only R/O to the same chunk of network, but not the sensitive stuff. We still need a "full R/W" (tier3/architects) and "full R/O" (probably won't get used much, but just in case) as well. I'm now trying to figure out the "best" / most elegant way to handle this new requirement. I'm thinking that it would be helpful to use group nesting to make things more simple on the LDAP side, but I don't know if LDAP-Group[*] will catch nested membership (user is a member of group X, which is a member of group Y - does LDAP-Group show group Y)? I'll also need to define the "sensitive" gear somehow (either via NAS name or IP address as defined in clients.conf) and return a reject for those cases... Below is the entirety of my "group_authz" file from policy.d/ so you know what I'm doing now. I am far from a developer, so I'm open to any and all ideas to improve this (in case I've done something stupid), as well as meet my new "tiered" requirements. Thanks all! Braden McGrath braden@big-geek.net group_authz { if (&LDAP-Group[*] == "cn=network-fulladmin,${modules.ldap.group.base_dn}") { switch "%{client:nas_type}" { case mikrotik { # For Mikrotik routers update reply { &Mikrotik-Group := "full" } return } case dell_os10 { # For Dell OS10 switches update reply { &Cisco-AVPair += 'shell:roles="sysadmin"' } return } case juniper { # For Juniper / JunOS devices update reply { &Juniper-Local-User-Name := "SUPER" } return } case cisco { update reply { &Cisco-AVPair += "shell:priv-lvl=15" # &Service-Type := "Administrative-User" } return } case awplus { # AlliedWare Plus - Allied Telesis switches (and others) # https://www.alliedtelesis.com/sites/default/files/documents/configuration-gu... # NOTE: AWPlus treats any Service-Type other than "Administrative-User" or "NAS-Prompt-User" as ACCESS-REJECT update reply { &Service-Type := "Administrative-User" } return } case { # default case - shouldn't ever match! update reply { &Reply-Message := "Could not find NAS_Type - check clients.conf?" } reject } } # debug_reply return } elsif (&LDAP-Group[*] == "cn=network-readonly,${modules.ldap.group.base_dn}") { switch "%{client:nas_type}" { case mikrotik { # Obviously, this group gets read access... update reply { &Mikrotik-Group := "read" } return } case dell_os10 { # On the Dells, this allows normal "op" actions but can't read/dump config update reply { &Cisco-AVPair += 'shell:roles="netoperator"' } return } case juniper { # true read-only for now, can use "OPR" if we want to allow clearing counters / etc update reply { &Juniper-Local-User-Name := "RO" } return } case cisco { update reply { # priv-lvl=1 is basic RO / not-enabled on Cisco, not "7" like Allied Telesis &Cisco-AVPair += "shell:priv-lvl=1" #&Service-Type := "NAS-Prompt-User" } return } case awplus { # this should be readonly/show commands for Allied Telesis # Definitely need both of these for priv7 level, drop the AVPair for just priv1 update reply { &Cisco-AVPair += "shell:priv-lvl=7" &Service-Type := "NAS-Prompt-User" } return } case { # default case, shouldn't ever match update reply { &Reply-Message := "Could not find NAS_Type - check clients.conf?" } reject } } return } else { update reply { &Reply-Message := "User not authorized - check directory/LDAP group membership" } reject } }
To get a user's nested group membership in the LDAP-Group attribute, you need to use the membership_filter configuration item, rather than membership_attribute, using the appropriate Active Directory extended match filter: membership_filter = "(member:1.2.840.113556.1.4.1941:=%{control:${..user_dn}})" This causes FreeRADIUS to perform a second query to find groups which a user is a member of, rather than reading the memberOf attribute from the user's object (which doesn't include nested groups). There is a caveat (down to Active Directory behaviour), the user's primary group is not returned with either technique, and equally, nested groups which the user's primary group is a member of will not be returned. That's just how Active Directory chooses to present group membership in LDAP queries. As for categorising your clients, you can add other values to the client definition e.g. client switches { ipaddr = 10.0.1.0/24 secret = XXX sensitive = yes } Then you can refer to %{client:sensitive} in your policy On 21/04/2022 23:53, Braden McGrath via Freeradius-Users wrote:
FR users & maintainers,
I have a functional FreeRADIUS installation (3.0.20 - I know it's slightly old, it's from the Ubuntu repo - on Ubuntu 20.04LTS), being used for management access and authorization on varied network equipment at an ISP (not everything supports TACACS so we're using RADIUS).
When I first setup this system, Alan and other members of this group gave me a bunch of help and I threw together a policy.d file (called "group_authz") that is querying for LDAP group membership, and then altering the replies back to the various NASes with specific attributes to grant read-only or read-write permissions on the devices. I was originally told by my boss that a universal read-only and read-write group would be sufficient, so it was pretty easy to check for membership in one of those two groups and then just send back specific attributes depending on %{client:nas_type}.
Of course, now the goalposts have shifted. I've been told that we need "tiered" access. Some users (tier 2 NOC team) should get R/W to most of the network, but should *not* have any access at all to some of the more sensitive gear (core/datacenter network stuff). Others (tier 1 NOC team) should get only R/O to the same chunk of network, but not the sensitive stuff. We still need a "full R/W" (tier3/architects) and "full R/O" (probably won't get used much, but just in case) as well.
I'm now trying to figure out the "best" / most elegant way to handle this new requirement. I'm thinking that it would be helpful to use group nesting to make things more simple on the LDAP side, but I don't know if LDAP-Group[*] will catch nested membership (user is a member of group X, which is a member of group Y - does LDAP-Group show group Y)? I'll also need to define the "sensitive" gear somehow (either via NAS name or IP address as defined in clients.conf) and return a reject for those cases...
Below is the entirety of my "group_authz" file from policy.d/ so you know what I'm doing now. I am far from a developer, so I'm open to any and all ideas to improve this (in case I've done something stupid), as well as meet my new "tiered" requirements.
Thanks all! Braden McGrath braden@big-geek.net
group_authz { if (&LDAP-Group[*] == "cn=network-fulladmin,${modules.ldap.group.base_dn}") { switch "%{client:nas_type}" { case mikrotik { # For Mikrotik routers update reply { &Mikrotik-Group := "full" } return }
case dell_os10 { # For Dell OS10 switches update reply { &Cisco-AVPair += 'shell:roles="sysadmin"' } return }
case juniper { # For Juniper / JunOS devices update reply { &Juniper-Local-User-Name := "SUPER" } return }
case cisco { update reply { &Cisco-AVPair += "shell:priv-lvl=15" # &Service-Type := "Administrative-User" } return }
case awplus { # AlliedWare Plus - Allied Telesis switches (and others) # https://www.alliedtelesis.com/sites/default/files/documents/configuration-gu... # NOTE: AWPlus treats any Service-Type other than "Administrative-User" or "NAS-Prompt-User" as ACCESS-REJECT update reply { &Service-Type := "Administrative-User" } return }
case { # default case - shouldn't ever match! update reply { &Reply-Message := "Could not find NAS_Type - check clients.conf?" } reject } } # debug_reply return } elsif (&LDAP-Group[*] == "cn=network-readonly,${modules.ldap.group.base_dn}") { switch "%{client:nas_type}" { case mikrotik { # Obviously, this group gets read access... update reply { &Mikrotik-Group := "read" } return } case dell_os10 { # On the Dells, this allows normal "op" actions but can't read/dump config update reply { &Cisco-AVPair += 'shell:roles="netoperator"' } return } case juniper { # true read-only for now, can use "OPR" if we want to allow clearing counters / etc update reply { &Juniper-Local-User-Name := "RO" } return }
case cisco { update reply { # priv-lvl=1 is basic RO / not-enabled on Cisco, not "7" like Allied Telesis &Cisco-AVPair += "shell:priv-lvl=1" #&Service-Type := "NAS-Prompt-User" } return }
case awplus { # this should be readonly/show commands for Allied Telesis # Definitely need both of these for priv7 level, drop the AVPair for just priv1 update reply { &Cisco-AVPair += "shell:priv-lvl=7" &Service-Type := "NAS-Prompt-User" } return }
case { # default case, shouldn't ever match update reply { &Reply-Message := "Could not find NAS_Type - check clients.conf?" } reject } } return } else { update reply { &Reply-Message := "User not authorized - check directory/LDAP group membership" } reject } } - List info/subscribe/unsubscribe? See http://www.freeradius.org/list/users.html
-- Nick Porter Porter Computing Ltd Registered in England No 12659380
On 4/22/22 14:21, Nick Porter wrote:
To get a user's nested group membership in the LDAP-Group attribute, you need to use the membership_filter configuration item, rather than membership_attribute, using the appropriate Active Directory extended match filter:
membership_filter = "(member:1.2.840.113556.1.4.1941:=%{control:${..user_dn}})"
Note that this matching rule only works with MS Active Directory, but not with other LDAP servers. Not sure whether the original poster uses MS AD. Ciao, Michael.
Replies inline... On Fri, Apr 22, 2022 at 8:21 AM Nick Porter <nick@portercomputing.co.uk> wrote:
To get a user's nested group membership in the LDAP-Group attribute, you need to use the membership_filter configuration item, rather than membership_attribute, using the appropriate Active Directory extended match filter:
This would be great, except I'm not using Active Directory (as Michael Ströder guessed). ;) 100% linux stack here, since my employer is a nonprofit and nobody wants to spend money for a few MS licenses just for Active Directory (even though those licenses are comparatively cheap for a nonprofit). The LDAP backend is FreeIPA, which runs "389ds," which from my understanding is pretty similar (but maybe not identical) to OpenLDAP.
As for categorising your clients, you can add other values to the client definition e.g.
client switches { ipaddr = 10.0.1.0/24 secret = XXX sensitive = yes }
Then you can refer to %{client:sensitive} in your policy This sort of thing is what I was leaning to already, thank you for confirming this will work.
If I can figure out nested groups with FreeIPA, I might do that, but at this point it's probably quicker to just bang out a bunch of unlang if's and some case statements for the special tier and be done with it... It would be a hell of a lot more elegant (IMO) to have individual LDAP groups granting RO or RW access to each type of device, and then having a user-facing group that is a member of various "Device-level" groups as needed... but it's probably also overcomplicating and overthinking things a bit.
Mandi! Braden McGrath via Freeradius-Users In chel di` si favelave...
100% linux stack here, since my employer is a nonprofit and nobody wants to spend money for a few MS licenses just for Active Directory
You can use Samba to create a fully compatible Active Directory Domain Controller AD-DC) now. If you have also MS Windows machine, consider it because sooner or later support for ''NT'' domains will be withdrawn in Microsoft client OS... -- Ubuntu in Zulu significa "Non so usare Debian". (cit. CtRiX)
Mandi! Nick Porter In chel di` si favelave...
There is a caveat (down to Active Directory behaviour), the user's primary group is not returned with either technique, and equally, nested groups which the user's primary group is a member of will not be returned. That's just how Active Directory chooses to present group membership in LDAP queries.
...but consider that in AD the default policy is to add users to 'Domain Users' group: https://docs.microsoft.com/en-us/windows/security/identity-protection/access... so if you keep 'Domain Users' as default group and user only other group for membership ''filter'' (in loose sense), you are OK... -- Errare è umano, ma per fare veramente casino ci vuole la password di root (Zio Budda)
participants (4)
-
Braden McGrath -
Marco Gaiarin -
Michael Ströder -
Nick Porter