[RADIATOR] add Attributes when retrying to a new Host in AuthROUNDROBIN
David Zych
dmrz at illinois.edu
Mon Aug 18 17:00:48 CDT 2014
I'm using Radiator with ntlm_auth (on linux) to authenticate against Active Directory. Occasionally ntlm_auth is slow to return, causing a logjam in the request queue and making it hard for new wireless users to authenticate. I've seen this happen in a variety of different ways, but some of those ways can be mitigated by using multiple instances of Radiator on each server: a single front-end instance proxies the final inner authentication piece to one of several back-end instances (each with its own separate ntlm_auth process).
When a stalled back-end instance becomes unstalled (after spending e.g. 6s waiting for ntlm_auth to respond to a single request), it's likely to have a bunch more requests already queued up, and some of those may already be old enough that there's no point in answering them (because the front-end instance will have already given up). We want a way for the back-end instance to recognize and IGNORE those requests, so that we can quickly get back to processing more recent requests.
I've figured out a first approximation of this in my config snippets below: the front-end instance adds the current time as a custom request attribute (X-Timestamp) before proxying it, and each back-end instance uses a hook to compare X-Timestamp to the (new) current time and short-circuit IGNORE anything whose X-Timestamp is older than 5 seconds. This works as desired for the _first_ back-end instance I proxy to, but if the first one times out, then the second back-end instance will still see the same X-Timestamp value and will also short-circuit IGNORE. Of course I could invoke my hook with a higher tolerance (e.g. allow up to 7s) to allow a retry to succeed, but then the first back-end instance would waste effort answering 6s-old requests.
How can I set a new attribute value on a request _each_ time I attempt to proxy it using AuthRADIUS and friends? I'm thinking a "PreForwardHook" would be ideal, but I don't see anything like that currently implemented. Is there another solution I'm not seeing, or if not, would it be possible to add such a hook?
(note: for my use case it wouldn't matter whether the hook also gets called between successive retries for the same Host, all I care about is that it's called each time we switch to a new Host)
As an aside: is there any special reason that MaxTargetHosts is unique to AuthVOLUMEBALANCE? I would think it would be equally applicable to all flavors of AuthRADIUS (and in particular I wish it was implemented in AuthROUNDROBIN).
Thanks,
David
Front-end instance:
# proxy inner auth to local sub-instances using simple round robin
# (since they are interchangeable and stateless). IGNORE acct.
<AuthBy ROUNDROBIN>
Identifier wireless-authproxy
include %D/private/localhost.secret
# give up if sub-instance doesn't respond within this time
RetryTimeout 5
# do not retry against the same sub-instance
Retries 0
FailureBackoffTime 1
<Host 127.0.0.1>
AuthPort %{GlobalVar:radius.wireless1.authport}
</Host>
<Host 127.0.0.1>
AuthPort %{GlobalVar:radius.wireless2.authport}
</Host>
<Host 127.0.0.1>
AuthPort %{GlobalVar:radius.wireless3.authport}
</Host>
<Host 127.0.0.1>
AuthPort %{GlobalVar:radius.wireless4.authport}
</Host>
# this attribute is not in the dictionary
StripFromRequest ConvertedFromEAPMSCHAPV2
IgnoreAccounting
AddToRequest X-Client-Identifier=%{Client:Identifier},X-Timestamp=%t
</AuthBy>
Back-end instances:
<AuthBy GROUP>
AuthByPolicy ContinueWhileAccept
# ...snip...
# IGNORE requests older than wireless-authproxy RetryTimeout
<AuthBy INTERNAL>
Identifier wireless-ignoreExpired
AuthHook sub { CITES::timestamp_within("X-Timestamp", 5, @_) }
</AuthBy>
<AuthBy NTLM>
# ...snip...
</AuthBy>
</AuthBy>
# AuthHook which returns ACCEPT if request's $attrname attribute has
# an epoch timestamp value within $window seconds of the current time,
# and IGNORE otherwise. Local sub-instances can use this with AuthBy
# INTERNAL (and a suitable AuthByPolicy) to proactively drop proxied
# requests that are already too old to be worth processing.
sub timestamp_within {
my ($attrname,$window) = (shift,shift);
my ($p,$rp)=@_;
my $ts = $p->get_attr($attrname) or
&main::log($main::LOG_ERR,"Missing $attrname in timestamp_within");
my $age = time - ($ts || 0);
return $main::ACCEPT if $age >= 0 && $age <= $window;
&main::log($main::LOG_ERR,"Ignoring expired request for ".$p->getUserName().": $attrname is ${age}s old");
return $main::IGNORE;
}
More information about the radiator
mailing list