Skip to content

Secure and monitor IPC connections

This page explains how to secure a q server with authentication and authorization hooks, and how to monitor and interrupt IPC connections in production.

A q process with an open port accepts whatever a client that can connect chooses to send. Interprocess communication (IPC) has no security of its own. By default it evaluates that input, so authentication and authorization are what stand between a listening port and arbitrary code execution.

Authentication and authorization

q provides callbacks in the .z namespace for your own security logic.

Authentication

For basic username and password authentication, start the q process with a credentials file, using the -u or -U option. q then validates each client's credentials against that file as it connects.

For anything more, such as Lightweight Directory Access Protocol (LDAP) or Kerberos, override the .z.pw callback. It receives the username and password, and returns a boolean that either admits or rejects the connection.

Authorization

Once a user is authenticated, use the .z.pg (synchronous) and .z.ps (asynchronous) callbacks for finer-grained permissions. Both can restrict access to particular functions or data based on the user, .z.u, or the connection handle, .z.w.

The server below admits an allowlist of functions and rejects everything else. It parses the incoming request into a parse tree, walks the tree, and checks every function it finds:

q)\p 5000

q)allowedFns: (`func1; `func2; `func3; +; -)   / the functions a client may call

/ signal an error if a call is not allowed
q)checkFn: {if[not x in allowedFns; '(.Q.s1 x), " not allowed"]}

/ walk the parse tree: check the head of each general list, then recurse
q)validatePT: {if[0h = type x; if[(not 0h = type first x) & 1 = count first x; checkFn first x]; .z.s each x where 0h = type each x]}

/ parse a string request, validate it, then evaluate it
q).z.pg: {if[10h = type x; x: parse x]; validatePT x; eval x}

A client that calls a function outside the allowlist gets an error instead of a result:

q)h: hopen `::5000
q)h"1+1"   / allowed
2
q)h"1*1"   / rejected by the custom .z.pg
'* not allowed
  [0]  h"1*1"
       ^

Weigh the check against the message rate

Every message pays for this validation. On a high-volume feed — a tickerplant publishing through .z.ps, for example — consider skipping the check for handles you have already established as trusted, rather than validating every update.

Monitor connections

Track open connections

To inspect the connections a server holds:

  • .z.H returns the active socket handles
  • -38!x returns a dictionary describing the open sockets in detail

For your own tracking, maintain a table of connected clients from the .z.po (open) and .z.pc (close) callbacks.

Interrupt a request

To abandon a long-running synchronous query, send an interrupt signal to the client process, such as kill -s INT <PID>. The client receives an 'rcv handle error, and the handle becomes unusable:

q)h"system\"sleep 30\""   / a long-running query blocks the client

Interrupting the client process from another terminal ends the wait, and the client console reports:

'rcv handle: 4. OS reports: Interrupted system call
  [0]  h"system\"sleep 30\""
       ^

The handle is broken from that point on, so reconnect before you send anything else:

q)h"1+1"
'Cannot write to handle 4. OS reports: Bad file descriptor
  [0]  h"1+1"
       ^

The interrupt stops the client waiting, not the server working

The server carries on evaluating the query it was sent, so the interrupt doesn't free the server. To cap server-side work, start the server with the -T timeout option, which abandons a query that runs too long.

Next steps