[RADIATOR] AuthBy FILE + IfNotExist and/or maintaining Access response
Nathan Anderson
nathana at fsr.com
Thu Oct 22 06:34:02 CDT 2009
Hola.
I've got a frustrating authentication problem that I have to solve, and although we have grown to love Radiator to death, I cannot seem to find a way to express the logic of my request in such a way that all conditions are met. I can think of two simple features that might make my problems go away.
Here is the situation: we want centralized authentication for administrative access to network equipment, as well as authorization (privilege level). I've got Radiator querying an LDAP server (Windows AD, actually) two times with different SearchFilters in order to check AD group membership. We also desire to have a small list of accounts outside of AD in a simple flat text file (less than 10). I have all of this working by stacking the two LDAP AuthBys and FILE AuthBy back-to-back using the ContinueUntilAccept policy. So far, so good:
RADIUS.CFG
----------
<AuthBy LDAP2>
Identifier Staff-Admin
[...]
SearchFilter (&(memberOf=CN=Admins,CN=Users,DC=domain,DC=local)(%0=%1))
</AuthBy>
<AuthBy LDAP2>
Identifier Staff-Normal
[...]
SearchFilter (&(memberOf=CN=Staff,CN=Users,DC=domain,DC=local)(%0=%1))
</AuthBy>
<AuthBy FILE>
Identifier Global-Users
Filename RADIUS-GLOBALS
</AuthBy>
<Handler>
AuthByPolicy ContinueUntilAccept
AuthBy Staff-Admin
AuthBy Staff-Normal
AuthBy Global-Users
</Handler>
The basic problem that I now face is that I have three different pieces of networking equipment that interpret a given RADIUS reply attribute differently, and I have not found a way that I can format the reply packet in such a way that it satisfies all three devices. So I need to massage the reply packet based on what kind of equipment I think the request came from. I have determined a way to reliably identify a particular device, and now I need to be able to change what needs changing.
(you can skip the following if you aren't terribly interested in the details...you can pick up reading again when you see my written expression of frustration: "ARGH")
I have three ethernet switches made by three different manufacturers: Cisco, Dell, and SMC. All are very IOS-like (well, the Cisco is, obviously). Out of the three, only the Dell can reliably be identified based on the form of its RADIUS authentication request.
I can pass authorization parameters to the switches using one of two RADIUS reply attributes:
Service-Type
cisco-avpair
Generally speaking, documentation suggests that the attributes SHOULD be interpreted thusly:
Service-Type:
- when it equals Login-User, the device grants base privilege level
- when it equals Administrative-User, the device grants elevated (level 15/"enable") privilege level
cisco-avpair:
- is really just a string consisting of TACACS+ value pairs concatenated together (I think)
- when it equals at least "shell:priv-lvl=1", the device grants base privilege level
- when it equals at least "shell:priv-lvl=15", the device grants elevated ("enable") privilege level
In reality, the different switches interpret the contents of these attributes in slightly different ways (which I discovered after a lot of trial and error):
The Dell switches use cisco-avpair solely to determine privilege level. If Service-Type is omitted completely, it works fine. The unfortunate quirk is that if Service-Type is PRESENT and equals Login-User, then the switch will claim that authentication fails. If it is present and equals Administrative-User, authentication passes and it still uses cisco-avpair to determine the appropriate privilege-level.
With the Cisco, I found that (at least with the version of IOS we are running) cisco-avpair is again the only reliable way to actually enforce the privilege level. However, unlike with the Dell, the Service-Type attribute MUST be present if RADIUS authorization is enabled in the Cisco config, otherwise authentication fails. Unlike the Dell, it is perfectly fine with being sent Service-Type=Login-User, but it CANNOT be omitted.
The SMC switches do NOT look at cisco-avpair at all. They enforce base-level/read-only privileges if Service-Type=Login-User and grant "enable" access if Service-Type=Administrative-User.
Thus my dilemma. I cannot leave off Service-Type or else I can't log into the Ciscos (not to mention that I am given the wrong privilege level when I log into the SMC). If I include Service-Type but properly set it to Login-User for accounts that shouldn't have enable access, then the Dells fail. If I leave Service-Type as Administrative-User across the board to pacify the Dells (which doesn't mess with the Cisco at all; it wants to see it present but it looks to cisco-avpair to actually set privileges), then EVERYBODY gets write access to the SMC switches.
ARGH.
(okay, those of you who just want the [relatively] short version can tune back in now)
So the plan of attack is that when I encounter the one device I can reliably identify based on its RADIUS request (the Dell switches), I send Service-Type=Adminstrative-User in the Access-Accept response; in all other cases I leave it alone.
Radiator doesn't have anything in the way of conditional expressions that I've been able to find (if...then), so I cheated and instead created a second AuthBy FILE that has a DEFAULT entry that checks for the identifying mark of the Dells and sets Service-Type appropriately; in order to keep the ContinueUntilAccept policy rolling along, I also set Auth-Type to Ignore and place this "special" FILE AuthBy block at the very top of my Handler:
RADIUS.CFG
----------
<Handler>
AuthByPolicy ContinueUntilAccept
<AuthBy FILE>
Filename RADIUS-DELLFIX
</AuthBy>
AuthBy Staff-Admin
AuthBy Staff-Normal
AuthBy Global-Users
</Handler>
RADIUS-DELLFIX
--------------
DEFAULT cisco-avpair = "shell:priv-lvl=1", Auth-Type = Ignore
Service-Type = Administrative-User
(Yes, believe it or not: the identifying mark of the Dell is that it actually SENDS a "cisco-avpair" attribute as a check attribute in the request. Why? Who knows.)
In my two LDAP AuthBys, I have an AddToReplyIfNotExist that sets Service-Type appropriately assuming that the switch is not a Dell, but does not override what the special Dell-checking FILE AuthBy already set for that value, if in fact it was set ("IfNotExist"):
RADIUS.CFG
----------
<AuthBy LDAP2>
Identifier Staff-Admin
[...]
SearchFilter (&(memberOf=CN=Admins,CN=Users,DC=domain,DC=local)(%0=%1))
AddToReply cisco-avpair=shell:priv-lvl=15
AddToReplyIfNotExist Service-Type=Administrative-User
</AuthBy>
<AuthBy LDAP2>
Identifier Staff-Normal
[...]
SearchFilter (&(memberOf=CN=Staff,CN=Users,DC=domain,DC=local)(%0=%1))
AddToReply cisco-avpair=shell:priv-lvl=1
AddToReplyIfNotExist Service-Type=Login-User
</AuthBy>
This works GREAT for the LDAP users! Everything goes as planned:
(base-privilege LDAP account, Dell switch)
------------------------------------------
Code: Access-Accept
Attributes:
Service-Type = Administrative-User
cisco-avpair = "shell:priv-lvl=1"
The handful of users in the FILE AuthBy I mentioned at the beginning (RADIUS-GLOBALS/Global-Users) aren't so lucky, however. I can find no equivalent to "AddToReplyIfNotExist" for the reply values in the FILE, so Service-Type gets included in the reply packet TWICE:
(base-privilege FILE account, Dell switch)
------------------------------------------
Code: Access-Accept
Attributes:
Service-Type = Administrative-User
Service-Type = Login-User
cisco-avpair = "shell:priv-lvl=1"
Like I explained in the section you probably skipped over ;), this looks like it should work but the "Service-Type=Login-User" causes the Dell to freak and it does not continue the authentication.
I could solve this problem one of two ways:
1. In testing, I discovered that the Dell switches apparently only pay attention to the last Service-Type attribute value in the response, so if I could move the Dell "packet mangler" FILE AuthBy to the end of the Handler, I'd be fine:
(base-privilege FILE account, Dell switch)
------------------------------------------
Code: Access-Accept
Attributes:
Service-Type = Login-User
cisco-avpair = "shell:priv-lvl=1"
Service-Type = Administrative-User
But even with ContinueWhileAccept or ContinueUntilReject, I can't do that, because regardless of what order I have the LDAP and FILE AuthBys in, if the user isn't in all of them, eventually a Reject will happen, and it's game over.
Proposed solution: a new AuthByPolicy that goes through all AuthBys without stopping, but if it encounters an Accept, that Accept cannot be overridden by a later Reject or Ignore...Accepts are "locked-in" once granted.
2. If only reply attributes inside of a FILE that were identical in name to one(s) already added to the reply packet before it reached the FILE didn't stack on top of previous ones, but were just simply ignored...
(sample entry from RADIUS-GLOBALS)
----------------------------------
fred Password = "flinstone"
cisco-avpair = "shell:priv-lvl=1",
Service-Type = Login-User
-------------^ if this was already set earlier, I want this to be ignored
Proposed solution: a new option flag for AuthBy FILE (say, "AddAttrIfNotExist") that told the AuthBy FILE module to not add to or override previous reply attributes that have already been set.
In a similar vein, I belive that FreeRADIUS's flat-text file format allows for different operators that cause the reply attribute to be treated in more fine-grained ways (I think the way it works is that the '+=' stacks reply attributes, much like what Radiator is currently doing; ':=' overrides an existing reply attribute; and '=' works like how I envision my hypothetical "AddAttrIfNotExist" flag would). Perhaps a better suggestion would be to have an AuthBy FREERADIUSFILE module that allowed you to use this syntax, or implement more operators in the current AuthBy FILE?
Also, while I'm dreaming :), I was thinking it might be nice to have an AuthBy module that allowed you to use AuthBy FILE syntax in-line with the actual configuration file, only because it seems silly for me to have to use a separate two-line file with a single DEFAULT user to smooth over the issue with the Dell switches:
(hypothetical example, RADIUS.CFG)
----------------------------------
<AuthBy INLINE>
DEFAULT cisco-avpair = "shell:priv-lvl=1", Auth-Type = Ignore
Service-Type = Administrative-User
</AuthBy>
Are there other options open to me that I have overlooked?
Thanks for your time, everyone!
--
Nathan Anderson
First Step Internet, LLC
nathana at fsr.com
More information about the radiator
mailing list