Skip to content
78 changes: 78 additions & 0 deletions code/handlers/ldap.q
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
// Functionality to aunthenticate user against LDAP server
// User attempts are cached
// This is used to allow .z.pw to be integrated with ldap

\d .ldap

enabled:@[value;`enabled;.z.o~`l64] / whether authentication is enabled
lib:`$getenv[`KDBLIB],"/",string[.z.o],"/torqldap"; / ldap library location
debug:@[value;`debug;0i] / debug level for ldap library: 0i = none, 1i=normal, 2i=verbose
server:@[value;`server;"localhost"]; / name of ldap server
port:@[value;`port;0i]; / port for ldap server
blocktime:@[value;`blocktime; 0D00:30:00]; / time before blocked user can attempt authentication
checklimit:@[value;`checklimit;3]; / number of attempts before user is temporarily blocked
checktime:@[value;`checktime;0D00:05]; / period for user to reauthenticate without rechecking LDAP server
buildDNsuf:@[value;`buildDNsuf;""]; / suffix used for building bind DN
buildDN:@[value;`buildDN;{{"uid=",string[x],",",buildDNsuf}}]; / function to build bind DN

out:{if[debug;:.lg.o[`ldap] x]};
err:{if[debug;:.lg.e[`ldap] x]};

init:{[lib] / initialise ldap library
.ldap.authenticate:lib 2:(`authenticate;1);
};

cache:([user:`$()]; pass:(); server:`$(); port:`int$(); time:`timestamp$(); attempts:`long$(); success:`boolean$(); blocked:`boolean$()); / create table to store login attempts

unblock:{[usr]
if[-11h<>type usr; :.ldap.out"username must be passed as a symbol"];
if[.ldap.cache[usr;`blocked];
update attempts:0, success:0b, blocked:0b from `.ldap.cache where user=usr;
:.ldap.out "unblocked user ",string usr;
];
};

login:{[user;pass] / validate login attempt
incache:.ldap.cache user; / get user from inputs
dict:`version`server`port`bind_dn`pass!(.ldap.version;.ldap.server;.ldap.port;.ldap.buildDN user;pass);

if[incache`blocked;
if[null blocktime; / if null blocktime then user is blocked
.ldap.out"authentication attempts for user ",dict[`bind_dn]," are blocked";
:0b];
$[.z.p<bt:incache[`time]+.ldap.blocktime; / block user if blocktime has not elapsed
[.ldap.out"authentication attempts for user ",dict[`bind_dn]," are blocked until ",string bt; :0b];
update attempts:0, blocked:0b from `.ldap.cache where user=user];
];

authorised:$[all ( / check if previously used details match
incache[`success]; / previous attempt was a success
incache[`time]>.z.p-.ldap.checktime; / previous attemp occured within checktime period
incache[`pass]~np:md5 pass / same password was used
);
1b;
@[{.ldap.authenticate[x]`success};dict;0b] / attempt authentication
];

`.ldap.cache upsert (user;np;`$.ldap.server;.ldap.port;.z.p; (1+0^incache`attempts;0) authorised;authorised;0b); / upsert details of current attempt

$[authorised; / display authentication status message
.ldap.out"successfully authenticated user ",;
.ldap.err"failed to authenticate user ",] dict`bind_dn;

if[.ldap.checklimit<=.ldap.cache[user]`attempts; / if attempt limit reached then block user
.[`.ldap.cache;(user;`blocked);:;1b];
.ldap.out"limit reached, user ",dict[`bind_dn]," has been locked out"];

:authorised;
};


if[enabled;
libfile:hsym ` sv lib,`so; / file containing ldap library
if[()~key libfile; / check ldap library file exists
:.ldap.err"cannot find library file: ",1_string libfile];
init hsym .ldap.lib; / initialise ldap library
.z.pw:{all(.ldap.login;x).\:(y;z)}@[value;`.z.pw;{[x;y]1b}]; / redefine .z.pw
];

1 change: 1 addition & 0 deletions code/handlers/order.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
dotz.q
ldap.q
writeaccess.q
controlaccess.q
permissions.q
Expand Down
13 changes: 13 additions & 0 deletions config/settings/default.q
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,19 @@ checkinterval:0D00:00:10 // how often heartbeats are checked
warningtolerance:2f // a process will move to warning state when it hasn't heartbeated in warningtolerance*checkinterval
errortolerance:3f // and to an error state when it hasn't heartbeated in errortolerance*checkinterval

\d .ldap

enabled:0b // whether ldap authentication is enabled
debug:0i // debug level for ldap library: 0i = none, 1i=normal, 2i=verbose
server:"localhost"; // name of ldap server
port:0i; // port for ldap server
version:3; // ldap version number
blocktime:0D00:30:00; // time before blocked user can attempt authentication
checklimit:3; // number of attempts before user is temporarily blocked
checktime:0D00:05; // period for user to reauthenticate without rechecking LDAP server
buildDNsuf:""; // suffix used for building bind DN
buildDN:{"uid=",string[x],",",buildDNsuf}; // function to build bind DN

// broadcast publishing
\d .u
broadcast:1b; // broadcast publishing is on by default. Availble in kdb version 3.4 or later.
Expand Down
49 changes: 49 additions & 0 deletions docs/handlers.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ off by setting .proc.loadhandlers to 0b in the configuration file.
| trackservers.q | .servers | Y | Discover and track server processes including name, type and attribute information. This also contains the core of the code which can be used in conjunction with the discovery service. | pc, timer |
| zpsignore.q | .zpsignore | N | Override async message handler based on certain message patterns | ps |
| writeaccess.q | .readonly | N | Restrict client write access to prevent any modification to data in place. Also disables all HTTP access. | pg, ps, ws, ph, pp |
| ldap.q | .ldap | N | Restrict client access to process using ldap authentication. | pw |



Expand Down Expand Up @@ -427,6 +428,54 @@ current user as listed on the gateway permission tables:
{.pm.allowed[.z.u;x[`0]] and x[`1] in `rdb}]


<a name="ldap"></a>

ldap.q
------

Authentication with an ldap server is managed with ldap.q. It allows:

- A user to authenticate against an ldap server;

- Caching of user attempts to allow reauthentication without server if within checktime period;

- Users to be blocked if too many failed authentication attempts are made.

Default parameters in the ldap namespace are set in {TORQHOME}/config/settings/default.q.

| parameter | description |
| :----------------: | :-----------------: |
| enabled | Whether ldap authentication is enabled |
| debug | Whether logging message are written to console |
| server | Host for ldap server. |
| port | Port number for ldap server. |
| version | Ldap version number. |
| blocktime | Time that must elapse before a blocked user can attempt to authenticate. If set to 0Np then the user is permanently blocked until an admin unblocks them. |
| checklimit | Login attempts before user is blocked. |
| checktime | Period of time that allows user to reauthenticate without confirming with ldap server. |
| buildDNsuf | Suffix for building distinguished name. |
| buildDN | Function to build distiniguished name. |

To get started the following will need altered from their default values: enabled, port, server, buildDNsuf.

The value buildDNsuf is required to build a users bind_dn from the supplied username and is called by the function buildDN. An example definition is:

.ldap.buildDNsuf:"ou=users,dc=website,dc=com";

Authentication is handled by .ldap.authenticate which is wrapped by .ldap.login, which is in turn wrapped by .z.pw when ldap authentication is enabled. When invoked .ldap.login retrieves the users latest authentication attempt from the cache, if it exists, and performs several checks before authenticating the user.

To authenticate the function first checks whether the user has been blocked by reaching the checklimit and blocktime has not passed, immediately returning false if this is the case. If the user has previously successfully authenticated within the period defined by checktime and is using the same credentials authentication will be permitted. For all other cases an authentication attempt will be made against the ldap server.

Example authentication attempt:

.ldap.login[`user;pass]
0b

To manually unblock a user the function .ldap.unblock must be passed their userame as a symbol. The function checks the cache to see whether a user is blocked and will reset the blocked status if necessary. An example usage of this function is:

.ldap.unblock[`user]


<a name="dia"></a>

Diagnostic Reporting
Expand Down
Binary file added lib/l64/torqldap.so
Binary file not shown.