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.

Dear cornelinux,
i have a similar problem.
In the definition of my userresolver I added the attribute “memberOf” and I am seeing the groups in the user details.
I configured also an authorization-policy with the action “add_user_in_response”.
When I test on the terminal with the httpie-Tool I see the following:

“detail”: {
“message”: “against userstore due to ‘NPS-2FA-Auth’”,
“threadid”: 139946387351296,
“user”: {
“email”: “xxx@yyyyy”,
“givenname”: “xxxxx”,
“group”: [
“CN=xxxxxxxxxx,OU=groups,OU=mitarbeiter,DC=uni-kiel,DC=de”,
“CN=xxxxxxxxxx,OU=dienste,DC=uni-kiel,DC=de”,
“CN=xxxxxxxxxx,OU=groups,OU=mitarbeiter,DC=uni-kiel,DC=de”,
“CN=xxxxxxxxxx,OU=rz_managed,OU=groups,OU=rz,OU=einrichtungen,DC=uni-kiel,DC=de”,
“CN=xxxxxxxxxx,CN=Users,DC=uni-kiel,DC=de”,
“CN=xxxxxxxxxx,CN=Users,DC=uni-kiel,DC=de”
],
“mobile”: “”,

I edited then the file /etc/privacyidea/rlm_perl.ini and added the following block

[Attribute Filter-Id]
######TEST
dir = user
userAttribute = group
regex = CN=(.*),OU=groups,OU=mitarbeiter,DC=uni-kiel,DC=de

But when I test with the radclient command I become the following:

Sent Access-Request Id 139 from 0.0.0.0:43917 to a.b.c.d:1812 length 48
User-Name = “xxxxxx”
User-Password = “xxxxxx”
Cleartext-Password = “xxxxxx”
Received Access-Accept Id 139 from a.b.c.d:1812 to aa.bb.cc.dd:43917 length 71
Reply-Message = “privacyIDEA access granted”
Filter-Id = “ARRAY(0x7f9ebc9a2620)”
Packet summary:
Accepted : 1
Rejected : 0
Lost : 0
Passed filter : 1
Failed filter : 0

I expected the complete CN as value for the Filter-Id, but I am becoming ARRAY(0x7f9ebc9a2620).

Any idea how to solve this problem?
Thanks a lot

Problem found…the freeradius Service must be restarted after editing the rlm_perl.ini.
Thanks.

Hi,

does this still work the same in 3.8.1? I’ve set up privacyIDEA with AD/LDAP and TOTP and that works fine with FortiGate VPN.

I need to restrict users per AD groups, and using multiple VPN tunnels, so I’m adding a group name beside the RADIUS server in Fortigate.
I added relevant section for Fortinet-Group-Name. For testing I didn’t even bother with regex yet but assigned a value directly to the AVP/VSA Fortinet-Group-Name.

[Mapping]
Fortinet-Group-Name = VPN_ADgroup (this is just for testing, I will use regex as there are more than one group/tunnel).

However, when using httpie on /validate/check it seems rlm_perl.ini is not processed, radiusd.log (set to debug) shows NO activity (just sits there, nothing comes up in the log), and there are no additional attributes in the response (as is in the post above from wwalker).

HTTP/1.1 200 OK
Cache-Control: no-cache
Connection: Keep-Alive
Content-Length: 1082
Content-Type: application/json
Date: Thu, 13 Jul 2023 11:12:19 GMT
Keep-Alive: timeout=5, max=100
Server: Apache

{
    "detail": {
        "message": "matching 1 tokens", 
        "otplen": 6, 
        "serial": "TOTPXXXXXXX", 
        "threadid": 140563761612544, 
        "type": "totp", 
        "user": {
            "email": "", 
            "givenname": "", 
            "memberOf": [
                "CN=VPNgroupname,CN=Users,DC=somedomain,DC=com", 
                "CN=Domain Users,CN=Users,DC=somedomain,DC=com"
            ], 
            "mobile": "", 
            "password": "", 
            "surname": "", 
            "username": "test2"
        }
    }, 
    "id": 2, 
    "jsonrpc": "2.0", 
    "result": {
        "authentication": "ACCEPT", 
        "status": true, 
        "value": true
    }, 
    "signature": "rsa_sha256.....

If I try to connect using VPN client and the request is sent by the firewall to the privacyIDEA then I do see radiusd.log processing, and I can see it assigns (at least it says it did) the attribute value. However, I’ve no idea what does it send back as I can’t see the response. And the VPN connection is not created with “wrong credentials” message from the VPN.

So, from the radiusd.log:

Thu Jul 13 12:28:43 2023 : Info: rlm_perl: +++ Map: Fortinet-Group-Name -> VPNADgroup
Thu Jul 13 12:28:43 2023 : Info: rlm_perl: return RLM_MODULE_OK
Thu Jul 13 12:28:43 2023 : Auth: (10) Login OK: [test2] (from client fw_iface port 0 some_IP)

VPN connection is still dropped even though VPNADgroup matches verbatim to what’s put in the FortiGate VPN’s user group using RADIUS and group.

What am I missing here? When requesting from the firewall it appears rlm_perl.ini is processed, but the firewall still dumps the connection with “wrong credentials” (although they’re correct, the AVP/VSA name and value are correct). It works without adding group with radius.

When I check /validate/check with httpie it seems rlm_perl.ini is not processes and I don’t see additional radius attributes in it - I’d love to be able to see what is sent back to the firewall so I can troubleshoot.
I did add the policy to include user details, mutlivalue memberOf and I can see those in the response to httpie, but no additional attributes from rlm_perl.ini when using httpie.

What am I missing here? It seems I missed some policy to have this working.

Thanks!!!