How to properly deal with HTTP 200 response with body with rlm_rest?
Hi, I have FreeRADIUS 3.0.17 set up to use rlm_rest to contact a custom REST server that does LDAP queries in the back and optionally does TOTP validation as well. The REST server then returns HTTP 204 when the request is accepted and HTTP 401 when it is rejected. So far this has been working great for pfSense OpenVPN authentication and WPA2-Enterprise, but now I want to use the same REST server to also handle FreeRADIUS requests for a Raritan Dominion KVM. The KVM says that I can optionally return a Filter-Id attribute containing a user group in the Access-Accept to authorize the authenticating user to access specific ports. Therefore, I've modified the REST server to return an HTTP 200 OK along with a JSON payload that contains the Filter-Id content I want to send back. When rlm_rest receives 204 it reurns the 'ok' code, but when receiving any other 2xx response it processes the body and returns the 'updated' code. Where I'm having an issue is, even after reading the Technical Guide, I'm having trouble wrapping my head around how I should properly deal with the 'updated' return code so that ultimately it is not the 'Post-Auth-Type REJECT' that gets called, but instead some other action where: 1) The request is seen as 'ok'/authorized, and 2) I can take the content of the Filter-Id and return it only with the Access-Accept message. Any guidance would be appreciated. Thanks, -Martin P.S. Find below the debug output of: a) a request with no returned body (and a resulting Access-Accept) b) a request with a returned Filter-Id body (and a resulting Access-Reject) -- Access-Accept (2) Received Access-Request Id 89 from 192.168.18.23:33311 to 192.168.40.34:1812 length 80 (2) NAS-Port-Type = Virtual (2) NAS-IP-Address = 192.168.18.23 (2) User-Name = "toto" (2) Acct-Session-Id = "koko1234" (2) User-Password = "fsdfsd" (2) # Executing section authorize from file /usr/local/etc/raddb/sites-enabled/default (2) authorize { (2) policy filter_username { (2) if (&User-Name) { (2) if (&User-Name) -> TRUE (2) if (&User-Name) { (2) if (&User-Name =~ / /) { (2) if (&User-Name =~ / /) -> FALSE (2) if (&User-Name =~ /@[^@]*@/ ) { (2) if (&User-Name =~ /@[^@]*@/ ) -> FALSE (2) if (&User-Name =~ /\.\./ ) { (2) if (&User-Name =~ /\.\./ ) -> FALSE (2) if ((&User-Name =~ /@/) && (&User-Name !~ /@(.+)\.(.+)$/)) { (2) if ((&User-Name =~ /@/) && (&User-Name !~ /@(.+)\.(.+)$/)) -> FALSE (2) if (&User-Name =~ /\.$/) { (2) if (&User-Name =~ /\.$/) -> FALSE (2) if (&User-Name =~ /@\./) { (2) if (&User-Name =~ /@\./) -> FALSE (2) } # if (&User-Name) = notfound (2) } # policy filter_username = notfound (2) [preprocess] = ok (2) [chap] = noop (2) [mschap] = noop (2) [digest] = noop (2) suffix: Checking for suffix after "@" (2) suffix: No '@' in User-Name = "sdfsdf", looking up realm NULL (2) suffix: No such realm "NULL" (2) [suffix] = noop (2) eap: No EAP-Message, not doing EAP (2) [eap] = noop (2) [files] = noop (2) [expiration] = noop (2) [logintime] = noop (2) if (User-Password) { (2) if (User-Password) -> TRUE (2) if (User-Password) { (2) update control { (2) Auth-Type := rest (2) } # update control = noop (2) } # if (User-Password) = noop (2) pap: WARNING: No "known good" password found for the user. Not setting Auth-Type (2) pap: WARNING: Authentication will fail unless a "known good" password is available (2) [pap] = noop (2) } # authorize = ok (2) Found Auth-Type = rest (2) # Executing group from file /usr/local/etc/raddb/sites-enabled/default (2) authenticate { (2) rest: Expanding URI components (2) rest: EXPAND http://127.0.0.1:9080 (2) rest: --> http://127.0.0.1:9080 (2) rest: EXPAND /user/%{User-Name}/mac/%{Called-Station-ID}?action=authenticate (2) rest: --> /user/sdfsdf/mac/?action=authenticate (2) rest: Sending HTTP POST to "http://127.0.0.1:9080/user/sdfsdf/mac/?action=authenticate" (2) rest: Encoding attribute "NAS-Port-Type" (2) rest: Encoding attribute "NAS-IP-Address" (2) rest: Encoding attribute "User-Name" (2) rest: Encoding attribute "Acct-Session-Id" (2) rest: Encoding attribute "User-Password" (2) rest: Encoding attribute "Event-Timestamp" (2) rest: Processing response header (2) rest: Status : 204 (No Content) (2) [rest] = ok (2) } # authenticate = ok (2) # Executing section post-auth from file /usr/local/etc/raddb/sites-enabled/default (2) post-auth { (2) update { (2) No attributes updated (2) } # update = noop (2) [exec] = noop (2) policy remove_reply_message_if_eap { (2) if (&reply:EAP-Message && &reply:Reply-Message) { (2) if (&reply:EAP-Message && &reply:Reply-Message) -> FALSE (2) else { (2) [noop] = noop (2) } # else = noop (2) } # policy remove_reply_message_if_eap = noop (2) } # post-auth = noop (2) Sent Access-Accept Id 89 from 192.168.40.34:1812 to 192.168.18.23:33311 length 0 (2) Finished request -- Access-Reject (4) Received Access-Request Id 212 from 192.168.18.23:56985 to 192.168.40.34:1812 length 81 (4) NAS-Port-Type = Virtual (4) NAS-IP-Address = 192.168.18.23 (4) User-Name = "toto" (4) Acct-Session-Id = "01dc5cb9b7469d20072c" (4) User-Password = "koko1234" (4) # Executing section authorize from file /usr/local/etc/raddb/sites-enabled/default (4) authorize { (4) policy filter_username { (4) if (&User-Name) { (4) if (&User-Name) -> TRUE (4) if (&User-Name) { (4) if (&User-Name =~ / /) { (4) if (&User-Name =~ / /) -> FALSE (4) if (&User-Name =~ /@[^@]*@/ ) { (4) if (&User-Name =~ /@[^@]*@/ ) -> FALSE (4) if (&User-Name =~ /\.\./ ) { (4) if (&User-Name =~ /\.\./ ) -> FALSE (4) if ((&User-Name =~ /@/) && (&User-Name !~ /@(.+)\.(.+)$/)) { (4) if ((&User-Name =~ /@/) && (&User-Name !~ /@(.+)\.(.+)$/)) -> FALSE (4) if (&User-Name =~ /\.$/) { (4) if (&User-Name =~ /\.$/) -> FALSE (4) if (&User-Name =~ /@\./) { (4) if (&User-Name =~ /@\./) -> FALSE (4) } # if (&User-Name) = notfound (4) } # policy filter_username = notfound (4) [preprocess] = ok (4) [chap] = noop (4) [mschap] = noop (4) [digest] = noop (4) suffix: Checking for suffix after "@" (4) suffix: No '@' in User-Name = "asdfasd", looking up realm NULL (4) suffix: No such realm "NULL" (4) [suffix] = noop (4) eap: No EAP-Message, not doing EAP (4) [eap] = noop (4) [files] = noop (4) [expiration] = noop (4) [logintime] = noop (4) if (User-Password) { (4) if (User-Password) -> TRUE (4) if (User-Password) { (4) update control { (4) Auth-Type := rest (4) } # update control = noop (4) } # if (User-Password) = noop (4) pap: WARNING: No "known good" password found for the user. Not setting Auth-Type (4) pap: WARNING: Authentication will fail unless a "known good" password is available (4) [pap] = noop (4) } # authorize = ok (4) Found Auth-Type = rest (4) # Executing group from file /usr/local/etc/raddb/sites-enabled/default (4) authenticate { (4) rest: Expanding URI components (4) rest: EXPAND http://127.0.0.1:9080 (4) rest: --> http://127.0.0.1:9080 (4) rest: EXPAND /user/%{User-Name}/mac/%{Called-Station-ID}?action=authenticate (4) rest: --> /user/asdfasd/mac/?action=authenticate (4) rest: Sending HTTP POST to "http://127.0.0.1:9080/user/asdfasd/mac/?action=authenticate" (4) rest: Encoding attribute "NAS-Port-Type" (4) rest: Encoding attribute "NAS-IP-Address" (4) rest: Encoding attribute "User-Name" (4) rest: Encoding attribute "Acct-Session-Id" (4) rest: Encoding attribute "User-Password" (4) rest: Encoding attribute "Event-Timestamp" (4) rest: Processing response header (4) rest: Status : 200 (OK) (4) rest: Type : json (application/json) (4) rest: Parsing attribute "Filter-Id" (4) rest: EXPAND Raritan:Gusergroup (4) rest: --> Raritan:Gusergroup (4) rest: Filter-Id := "Raritan:Gusergroup" (4) [rest] = updated (4) } # authenticate = updated (4) Failed to authenticate the user (4) Using Post-Auth-Type Reject (4) # Executing group from file /usr/local/etc/raddb/sites-enabled/default (4) Post-Auth-Type REJECT { (4) attr_filter.access_reject: EXPAND %{User-Name} (4) attr_filter.access_reject: --> asdfasd (4) attr_filter.access_reject: Matched entry DEFAULT at line 11 (4) [attr_filter.access_reject] = updated (4) [eap] = noop (4) policy remove_reply_message_if_eap { (4) if (&reply:EAP-Message && &reply:Reply-Message) { (4) if (&reply:EAP-Message && &reply:Reply-Message) -> FALSE (4) else { (4) [noop] = noop (4) } # else = noop (4) } # policy remove_reply_message_if_eap = noop (4) } # Post-Auth-Type REJECT = updated (4) Delaying response for 1.000000 seconds (4) (4) Discarding duplicate request from client wifi port 56985 - ID: 212 due to delayed response (4) Sending delayed response (4) Sent Access-Reject Id 212 from 192.168.40.34:1812 to 192.168.18.23:56985 length 20 (4) Cleaning up request packet ID 212 with timestamp +1997
Where I'm having an issue is, even after reading the Technical Guide, I'm having trouble wrapping my head around how I should properly deal with the 'updated' return code so that ultimately it is not the 'Post-Auth-Type REJECT' that gets called, but instead some other action where:
rest if (updated) { ok } -Arran
Hi Arran, Thanks for you response. I initially assumed that I should put: rest if (updated) { ok } in 'post-auth', so I put it right at the top of that section. Unfortunately it didn't change anything, so I'm assuming that if 'updated' is returned during authentication, then it's considered a reject and nothing can be done about this in post-auth. I then tried to put your suggested snippet in 'authenticate', but that prevented FreeRADIUS from starting at all (in line with the warning not to put Unlang in authenticate). Then I tried putting it in a policy file; FreeRADIUS wouldn't start. I finally tried to put it in the authorize section; FreeRADIUS started but it had not effect. Output when the snippet is in post-auth or authorize (basically, no difference from when it isn't): (0) [rest] = updated (0) } # authenticate = updated (0) Failed to authenticate the user (0) Using Post-Auth-Type Reject (0) # Executing group from file /usr/local/etc/raddb/sites-enabled/default (0) Post-Auth-Type REJECT { Honestly, I feel pretty stupid because I'm sure it's super simple and I'm just missing something obvious here, but where exactly am I supposed to put that snippet? Thanks, -Martin On Sun, Apr 21, 2019 at 12:34 PM Arran Cudbard-Bell <a.cudbardb@freeradius.org> wrote:
Where I'm having an issue is, even after reading the Technical Guide, I'm having trouble wrapping my head around how I should properly deal with the 'updated' return code so that ultimately it is not the 'Post-Auth-Type REJECT' that gets called, but instead some other action where:
rest if (updated) { ok }
-Arran
- List info/subscribe/unsubscribe? See http://www.freeradius.org/list/users.html
On Apr 22, 2019, at 12:12 PM, Martin Gignac <martin.gignac@gmail.com> wrote:
Hi Arran,
Thanks for you response. I initially assumed that I should put:
rest if (updated) { ok }
rest { updated = 1 } if (updated) { ok } It's because the default action for updated in authenticate is return, so it just exits the authenticate section. Authenticate in FreeRADIUS is meant to be for a module that runs locally on the server and does some sort of authentication by comparing the contents of the incoming packet with some secret information the server has access to. Local authentication modules don't usually return updated, which is why its counted as a failure if one does. There's no real issue doing what you're doing though, just explaining why it doesn't work out of the box and you need to jump through a couple of extra hoops. ...and arguably the action for updated probably shouldn't be return, as it strongly hints there are more modules that need to be called. I guess the return code priorities/actions were probably set in < v1.0.x, where there was no policy language, and there you really would only ever call one module in authenticate. So that leaves two more permanent fixes, either: - rlm_rest only returns "OK" for its authenticate method, which probably means it should just ignore body data. Then if you wanted to do what you're doing now you'd call rest.authorize instead. - The updated rcode in should be given a non 'return' priority. I don't know if we can make either of those changes in v3.0.x, but maybe we can change it in v4.0.x. Any though's Alan DeKok, Matthew Newton et al? -Arran
rest { updated = 1 } if (updated) { ok }
I'm still confused about which section the snippet should go into: 'authorize', 'authenticate' or 'post-auth'? Ultimately, is the goal of: if (updated) { ok } to change an rcode of updated to ok during auhorize, authenticate or post-auth?
It's because the default action for updated in authenticate is return, so it just exits the authenticate section.
According to Table 4.3.3.2 ("Action table for the Authenticate section") in the FreeRADIUS Technical Guide, the action is "continue (priority 1)". Is this the same as return? Thanks, -Martin
April 22, 2019 7:50 PM, "Arran Cudbard-Bell" <a.cudbardb@freeradius.org> wrote:
On Apr 22, 2019, at 12:12 PM, Martin Gignac <martin.gignac@gmail.com> wrote:
Hi Arran,
Thanks for you response. I initially assumed that I should put:
rest if (updated) { ok }
rest { updated = 1 } if (updated) { ok }
It's because the default action for updated in authenticate is return, so it just exits the authenticate section.
Authenticate in FreeRADIUS is meant to be for a module that runs locally on the server and does some sort of authentication by comparing the contents of the incoming packet with some secret information the server has access to. Local authentication modules don't usually return updated, which is why its counted as a failure if one does.
There's no real issue doing what you're doing though, just explaining why it doesn't work out of the box and you need to jump through a couple of extra hoops.
...and arguably the action for updated probably shouldn't be return, as it strongly hints there are more modules that need to be called. I guess the return code priorities/actions were probably set in < v1.0.x, where there was no policy language, and there you really would only ever call one module in authenticate.
So that leaves two more permanent fixes, either:
- rlm_rest only returns "OK" for its authenticate method, which probably means it should just ignore body data. Then if you wanted to do what you're doing now you'd call rest.authorize instead.
Reading https://github.com/FreeRADIUS/freeradius-server/blob/v3.0.x/raddb/mods-avail... and relating to HTTP 204 vs. 200 semantics I would think "updated" rcode is a natural fit for rest.authorize, right? I am also still unsure about rest.authenticate though.
- The updated rcode in should be given a non 'return' priority.
I don't know if we can make either of those changes in v3.0.x, but maybe we can change it in v4.0.x.
Any though's Alan DeKok, Matthew Newton et al?
-Arran
- List info/subscribe/unsubscribe? See http://www.freeradius.org/list/users.html
Hi Arran, Try as I might, I couldn't figure out where to put this successfully in sites-enabled/default:
rest { updated = 1 } if (updated) { ok }
What I ended up trying (and what worked for me) was that I modified rlm_rest.c to return 'ok' even in the presence of a body during the 'authenticate' phase: diff --git a/src/modules/rlm_rest/rlm_rest.c b/src/modules/rlm_rest/rlm_rest.c index 7cd1b45..2d9a033 100644 --- a/src/modules/rlm_rest/rlm_rest.c +++ b/src/modules/rlm_rest/rlm_rest.c @@ -513,7 +513,7 @@ static rlm_rcode_t CC_HINT(nonnull) mod_authenticate(void *instance, REQUEST *re ret = rest_response_decode(inst, section, request, handle); if (ret < 0) rcode = RLM_MODULE_FAIL; else if (ret == 0) rcode = RLM_MODULE_OK; - else rcode = RLM_MODULE_UPDATED; + else rcode = RLM_MODULE_OK; break; } else if (hcode < 500) { rcode = RLM_MODULE_INVALID; This is the only change I needed to have my scenario working. Indeed, the Filter-ID attribute is retrieved from the JSON payload of the HTTP response and is automatically appended to the returned Access-Accept message, as I needed it to be. Since I'm OK with having a modified rlm_rest module this appears the simplest way for me to get the required behavior in my situation. Thanks, -Martin
participants (3)
-
Arran Cudbard-Bell -
Martin Gignac -
Zenon Mousmoulas