Authorizing connections using ngx.fetch() as auth_request [stream/auth_request]

The example illustrates the usage of ngx.fetch() as an auth request analog in a stream block with a very simple TCP-based protocol: a connection starts with a magic prefix “MAGiK” followed by a secret 2 bytes. The preread_verify handler reads the first part of a connection and sends the secret bytes for verification to a HTTP endpoint. Later it decides based upon the endpoint reply whether to forward the connection to an upstream or reject the connection.

Step 1: Use the following commands to start your NGINX container with this lab’s files:

EXAMPLE='stream/auth_request'
docker run --rm --name njs_example  -v $(pwd)/conf/$EXAMPLE.conf:/etc/nginx/nginx.conf:ro -v $(pwd)/njs/:/etc/nginx/njs/:ro -p 80:80 -d nginx

Step 2: Now let’s use curl to test our NGINX server:

telnet 127.0.0.1 80
...
Hi
Connection closed by foreign host.

telnet 127.0.0.1 80
...
MAGiKQZ
BACKEND
Connection closed by foreign host.

telnet 127.0.0.1 80
...
MAGiKQQ
Connection closed by foreign host.

docker stop njs_example

Code Snippets

This config has both a stream block and an http block. The http block implements an “auth server” listing on port 8080. The stream block has two server blocks, one listening on port 80 that implements our MAGiK protocol and the other listens on port 8081 to serve as a simple upstream server.

nginx.conf
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
stream {
      js_path "/etc/nginx/njs/";

      js_import main from stream/auth_request.js;

      server {
            listen 80;

            js_preread  main.preread_verify;

            proxy_pass 127.0.0.1:8081;
      }

      server {
            listen 8081;

            return BACKEND\n;
      }
}

http {
      js_path "/etc/nginx/njs/";

      js_import main from stream/auth_request.js;

      server {
            listen 8080;

            server_name  aaa;

            location /validate {
                js_content main.validate;
            }
      }
}

This njs code has two functions. The first, preread_verify, will collect data from the preread buffer of the client connection looking for the two characters after the “MAGiK” prefix. It then uses ngx.fetch() to send those two characters to the auth server. If the auth server returns a 200 status code, the connection is allowed to continue. Anything else results in the connection being denied.

The validate function is used by the auth server to check the request body for the secret two byte code “QZ”. If the code is found a 200 is returned, if not a 403 is returned.

auth_request.js
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
function preread_verify(s) {
    var collect = '';

    s.on('upload', function (data, flags) {
        collect += data;

        if (collect.length >= 5 && collect.startsWith('MAGiK')) {
            s.off('upload');
            ngx.fetch('http://127.0.0.1:8080/validate',
                      {body: collect.slice(5,7), headers: {Host:'aaa'}})
            .then(reply => (reply.status == 200) ? s.done(): s.deny())

        } else if (collect.length) {
            s.deny();
        }
    });
}

function validate(r) {
        r.return((r.requestText == 'QZ') ? 200 : 403);
}

export default {validate, preread_verify};