AD Group Checking with RADIUS Attribute Mapping

When using a Fortinet firewall, you can specify a RADIUS group that the firewall will check if the user is a member of. It looks for the AVP Fortinet-Group-Name. I am trying to configure rlm_perl.ini to map the AD attribute memberOf to Fortinet’s AVP Fortinet-Group-Name. I’ve configured my LDAP resolver in the webGUI to map memberOf to Fortinet-Group-Name and can see it populating under a user’s profile in PrivacyIDEA. I then configured the following in rlm_perl.ini

[Attribute Fortinet-Group-Name]
dir = user
userAttribute = memberOf
regex = CN=(\w*),OU=groups,DC=example,DC=org

However, after running through a test authentication, the log reports the following:

Thu May 14 16:18:42 2020 : Info: rlm_perl: ++++++ searching in directory user
Thu May 14 16:18:42 2020 : Info: rlm_perl: +++++++ User attribute is a string:
Thu May 14 16:18:42 2020 : Info: rlm_perl: +++++++ trying to match
Thu May 14 16:18:42 2020 : Info: rlm_perl: ++++++++ Result: No match, no RADIUS attribute Fortinet-Group-Name added.

Where can I find information on the different…directives?.. to use under [Attribute Fortinet-Group-Name] as I think that’s where my problem lies and I have no idea what dir is actually telling the system to do.

If I get it properly passing group names, does it concatenate all the values it finds in $1 into a single string or maintain an array of strings?

First you need to add all user attributes to the privacyIDEA Response, that is sent to FreeRADIUS.

You need to do this with the authorization policy add_user_in_response.

When you have done this you can see all the user attributes in the REST response. Simply check this by manually calling /validate/check. Then you will be able to match the corresponding values. Have you done this?

Yes, I’ve got that policy configured
image

Take a look at the validate/check response.

Your RADIUS config expects something like:

{ detail: {
        user : {
             memberOf: ....
         }
  }
}

What’s a good way to make that call manually? When I use

echo 'User-Name=realm3\\cornelius, User-Password=test' | radclient -xs \
   127.0.0.1 auth test

I just get
image

This is what occurs in the logs, does this mean I don’t have something configured right in my perl.ini? It doesn’t look like it matches any attributes. when I clearly have a few.

$: "Fortinet-Group-Name": "CN=IT<Redacted>", "department": "<Redacted>", "username": "<Redacted>", "class": "OU<Redacted>", $
Fri May 15 09:42:48 2020 : Info: rlm_perl: privacyIDEA access granted
Fri May 15 09:42:48 2020 : Info: rlm_perl: ++++ Parsing group: Mapping
Fri May 15 09:42:48 2020 : Info: rlm_perl: +++++ Found member 'Mapping user'
Fri May 15 09:42:48 2020 : Info: rlm_perl: ++++++ Map: user : group -> Class
Fri May 15 09:42:48 2020 : Info: rlm_perl: ++++ Parsing group: Attribute
Fri May 15 09:42:48 2020 : Info: rlm_perl: +++++ Found member 'Attribute Class'
Fri May 15 09:42:48 2020 : Info: rlm_perl: ++++++ Attribute: IF ''->'class' == 'Users' THEN 'Class'
Fri May 15 09:42:48 2020 : Info: rlm_perl: ++++++ no directory
Fri May 15 09:42:48 2020 : Info: rlm_perl: +++++++ User attribute is a string:
Fri May 15 09:42:48 2020 : Info: rlm_perl: +++++++ trying to match
Fri May 15 09:42:48 2020 : Info: rlm_perl: ++++++++ Result: No match, no RADIUS attribute Class added.
Fri May 15 09:42:48 2020 : Info: rlm_perl: +++++ Found member 'Attribute framedIP'
Fri May 15 09:42:48 2020 : Info: rlm_perl: ++++++ Attribute: IF ''->'framedIP' == 'Users' THEN 'framedIP'
Fri May 15 09:42:48 2020 : Info: rlm_perl: ++++++ no directory
Fri May 15 09:42:48 2020 : Info: rlm_perl: +++++++ User attribute is a string:
Fri May 15 09:42:48 2020 : Info: rlm_perl: +++++++ trying to match
Fri May 15 09:42:48 2020 : Info: rlm_perl: ++++++++ Result: No match, no RADIUS attribute framedIP added.
Fri May 15 09:42:48 2020 : Info: rlm_perl: +++++ Found member 'Attribute Framed-Netmask'
Fri May 15 09:42:48 2020 : Info: rlm_perl: ++++++ Attribute: IF ''->'Framed-Netmask' == 'Users' THEN 'Framed-Netmask'
Fri May 15 09:42:48 2020 : Info: rlm_perl: ++++++ no directory
Fri May 15 09:42:48 2020 : Info: rlm_perl: +++++++ User attribute is a string:
Fri May 15 09:42:48 2020 : Info: rlm_perl: +++++++ trying to match
Fri May 15 09:42:48 2020 : Info: rlm_perl: ++++++++ Result: No match, no RADIUS attribute Framed-Netmask added.
Fri May 15 09:42:48 2020 : Info: rlm_perl: +++++ Found member 'Attribute Fortinet-Group-Name'
Fri May 15 09:42:48 2020 : Info: rlm_perl: ++++++ Attribute: IF 'user'->'memberOf' == 'CN=(\w*)<Redacted>' THEN 'Fortinet-Group-Name'
Fri May 15 09:42:48 2020 : Info: rlm_perl: ++++++ searching in directory user
Fri May 15 09:42:48 2020 : Info: rlm_perl: +++++++ User attribute is a string:
Fri May 15 09:42:48 2020 : Info: rlm_perl: +++++++ trying to match
Fri May 15 09:42:48 2020 : Info: rlm_perl: ++++++++ Result: No match, no RADIUS attribute Fortinet-Group-Name added.
Fri May 15 09:42:48 2020 : Info: rlm_perl: +++ Map: serial -> privacyIDEA-Serial
Fri May 15 09:42:48 2020 : Info: rlm_perl: return RLM_MODULE_OK

Perl Config

[Mapping]
serial = privacyIDEA-Serial

[Mapping user]
group = Class

[Attribute Class]
userAttribute = class
regex = Users

[Attribute framedIP]
radiusAttribute = framedIP
userAttribute = framedIP
regex = Users

[Attribute Framed-Netmask]
radiusAttribute = Framed-Netmask
userAttribute = Framed-Netmask
regex = Users

[Attribute Fortinet-Group-Name]
dir = user
userAttribute = memberOf
regex = CN=(\w*)<Redacted>

On a linux system I recommend the tool httpie.

Run

http --verify no POST https://localhost/validate/check user=cornelius@realm3 pass=test

and you will see the JSON response nicely formatted.

1 Like

That’s very useful, and makes the log entries seem contradictory, maybe I’m just not understanding what they mean. All attributes are being passed with proper values. The Fortinet-Group-Name is populating the wrong group but that’s just simple tweaking of the regular expression in use. Thanks for the help @cornelinux.

Server: Apache/2.4.29 (Ubuntu)
Strict-Transport-Security: max-age=63072000; includeSubdomains;

{
   "detail": {
       "message": "matching <redacted> tokens",
       "otplen": <redacted>,
       "serial": "<redacted>",
       "threadid": 139673592309504,
       "type": "<redacted>",
       "user": {
           "Fortinet-Group-Name": "<redacted>",
           "class": "OU=<redacted>",
           "framedIP": "",
       }
   },
   "id": 1,
   "jsonrpc": "2.0",
   "result": {
       "status": true,
       "value": true
   },
   "signature": "rsa_sha256_pss:<redacted>",
   "time": 1589556062.1862047,
   "version": "privacyIDEA 3.2.2",
   "versionnumber": "3.2.2"
}

1 Like

OK, just for the record, you need to configure something like this:

[Attribute Fortinet-Group-Name]
dir = user
userAttribute = Fortinet-Group-Name
regex = CN=(\w*)<Redacted>

Since the userAttribute is the attribute returned by the privacyIDEA API.

Thanks @cornelinux

what is the dir directive for, or where can I go to see what directives I can use and what they’ll do?

I think it is the top level node in the details node.
If there are ever other details (there are) but probably you only need dir=user.

OK, I’ve done some more testing with this and made some corrections, but hit another wall. First off, the value populating into Fortinet-Group-Name was only taking the first value from the AD attribute memberOf. This was because I didn’t configure my LDAP resolver to treat Fortinet-Group-Name as a multivalue attribute. After getting that out of the way, I set rlm_perl.ini to only the following:

[Attribute Fortinet-Group-Name]
radiusAttribute = Fortinet-Group-Name
userAttribute = Fortinet-Group-Name

Testing with that configuration, I was able to verify that all member groups were being passed in the RADIUS response.

Next up, I reintroduced the regular expression and verified it using regexr.com to make sure it acted as I expected it to. Unfortunately, it seems to have no effect and further testing shows all AD groups still being passed in the RADIUS response, this also includees using/not using the dir setting.

It seems the filter mangler isn’t mangling. Following the comments and examples from lines 20-37 here https://github.com/privacyidea/FreeRADIUS/blob/master/rlm_perl.ini, what should I be seeing with this config:

[Attribute Filter-Id]
dir = user
userAttribute = memberOf
radiusAttribute = Fortinet-Group-Name
regex = CN=(\w+),OU=example,DC=example,DC=org

A reponse with a RADIUS attribute of Filter-Id, a radiusAttribute Fortinet-Group-Name with the value in $1? What I’m getting is an array of values from memberOf.

No, you must not user “memberOf”. It looks like you map the LDAP attribute “memberOf” to the privacyIDEA Attribute “Fortinet-Group-Name”. “Fortinet-Group-Name” is contained in the REST response from privacyIDEA. So you need to configure

[Attribute Filter-Id]
dir = user
userAttribute = Fortinet-Group-Name
regex = 

“Filter-Id” will be the new RADIUS attribute
You need “dir = user”, to be “user” the sub node in “details”.
userAttribute is the key in response from privacyIDEA
You do not need the radiusAttribute, it would simply override the “Filter-Id”.

Your regex is checked against all elements in the list, returned by privacyIDEA.

You should see something in your log like

+++++++ User attribute is a list:

+++++++ trying to match

1 Like

I think I have it working now. Using NTRadPing I get the following response:
image

I wasn’t getting that unknown-vendor line in the response before. The reason I am using NTRadPing is that, and maybe I’m wrong, but httpie hits the endpoint over HTTP and not RADIUS, so I’m not going to see the RADIUS response details using it.

Here’s what my config, that seems to be working, looks like. I changed a few things around in the attribute mapping and the config in the perl config file to try and reduce confusion. I’m now mapping the AD attribute memberOf to PrivacyIDEA as memberOf (In LDAP Resolver, "memberOf" : "memberOf").

[Attribute Fortinet-Group-Name]
dir = user
userAttribute = memberOf
regex = CN=F<redacted>

and here’s the freeradius logs indicating a successful match.

Info: rlm_perl: +++++ Found member 'Attribute Fortinet-Group-Name'
Info: rlm_perl: ++++++ Attribute: IF 'user'->'memberOf' == 'CN=F(\w+)<redacted>' THEN 'Fortinet-Group-Name'
Info: rlm_perl: ++++++ searching in directory user
Info: rlm_perl: +++++++ User attribute is a list: ARRAY(0x7f59a4b4ba48)
Info: rlm_perl: +++++++ trying to match CN=I<redacted>
Info: rlm_perl: ++++++++ Result: No match, no RADIUS attribute Fortinet-Group-Name added.
Info: rlm_perl: +++++++ trying to match CN=F<redacted>
Info: rlm_perl: ++++++++ Result: Add RADIUS attribute Fortinet-Group-Name = F<redacted>

Thanks for the patience @cornelinux, your help was appreciated.

1 Like

Thanks a lot for the wrap up!

You can use httpie to check the response of privacyIDEA itself, as it is passed the FreeRADIUS.

Of course after this, you need to use a RADIUS client - like NTRadPing - or on a linux machine like the privacyIDEA server itself radclient:

echo "User-Name=hans, User-Password=test123456" | radclient -x -s localhost auth testing123

Follow-up question, didn’t really think it needed it’s own thread. @cornelinux, is it possible to include additional values from other attributes? The problem I face now is that in AD the user group Domain Users is not actually included in the AD attribute memberOf. So I was wondering if it was possible to do something like below. This doesn’t work, it appears to concatenate the settings into a single operation.

[Attribute Fortinet-Group-Name]
dir = user
userAttribute = memberOf
regex = CN=F<redacted>

[Attribute Fortinet-Group-Name]
dir = user
userAttribute = cn
regex = (example.org).*

I think multi value attributes only work within one group [Attribute Fortinet-Group-Name]. Without inspecting the code I would not know the outcome, if you have two groups with the same name.

eh, no worries. Turns out this VSA can’t be a multivalue attribute, it uses a string comparison and each time freeradius gives the fortigate a value, it overwrites the previous value.