Node Servers at SELinux MLS Level

Current xinetd functionality allows you to start a server at the level of a connection but it is limited to only servicing that connection. Apache 1.3 (configured with ‘ServerType inetd’) can be run by xinetd this way quite effectively however it is exec’d for each connection. For this experiment I’m using RHEL6 with selinux-policy-mls installed and to keep it simple I’m running SELinux in permissive mode. Configuration of labeled networking was accomplished as follows:

1. Create a CIPSO DOI so that the system can understand packets (both inbound
and outbound) that have a CIPSO DOI of "2".

 # netlabelctl cipsov4 add local doi:2

2. In this example we are going to use address selectors to ensure we only
send CIPSO "local" traffic to localhost so we need to delete the existing
legacy domain selector and re-create it with the address selectors:

 # netlabelctl map del default
 # netlabelctl map add default address:0.0.0.0/0 protocol:unlbl

3. We need to tell the system that for all the packets destined for localhost
we want to assign CIPSO tags/labels using DOI "2":

 # netlabelctl map add default address:127.0.0.1 protocol:cipsov4,2

node-webworker by Peter Griess is utilized to implement a super servers that runs servers at level that can process multiple connection and my node-selinux module for doing SELinux stuff.

The master:

var path = require('path');
var Worker = require('webworker/webworker').Worker;
var sys = require('sys');
var net = require('net');
var selinux = require('selinux_node');

var workers = new Array();

var s = new selinux.SELinux();

var srv = net.createServer(function (stream) {
    stream.pause();
    sys.puts("connection " + stream.fd);

    s.getpeercon(stream.fd, function (con) {
        var w;
    	if (typeof(workers[con]) == 'undefined') {
	   sys.puts("create new worker for context " + con);
	   s.setexeccon(con);
	   w = new Worker(path.join(__dirname, 'xworker.js'));
	   workers[con] = w;
    	}
	else {
	     w = workers[con];
	     sys.puts("use existing worker ", w);
    	}

    	w.postMessage({ 'banner' : 'Hello, world!' }, stream.fd);
    }
);
}).listen(5000, "127.0.0.1");

The worker:

var assert = require('assert');
var http = require('http');
var sys = require('sys');
var net = require('net');

var banner = undefined;

var srv = http.createServer(function(req, resp) {
    resp.writeHead(200, {'Content-Type' : 'text/plain'});
    resp.write(banner + ' (pid ' + process.pid + ')\n');
    resp.end();
});

onmessage = function(msg) {
    assert.ok(msg.fd && msg.fd > 0);

    sys.print('Got message!\n');

    banner = msg.data.banner;

    var s = new net.Stream(msg.fd);
    s.type = srv.type;
    s.server = srv;
    s.resume();
    srv.emit('connection', s);
};

Test can be done simply by using curl and newrole for example:

curl http://127.0.0.1:5000

To test at different levels run:

newrole -l s0-s0 -- -c "curl http://127.0.0.1:5000"

Server test output:

[tedx@localhost ~]$ node fdmaster.js
connection 6
create new worker for context staff_u:staff_r:staff_t:SystemLow-SystemHigh
Got message!
connection 9
create new worker for context staff_u:staff_r:staff_t:SystemLow
Got message!
connection 12
create new worker for context staff_u:staff_r:staff_t:UNCLASSIFIED
Got message!
connection 15
use existing worker
[object Object]
Got message!

Test run output:

[tedx@localhost ~]$ curl http://127.0.0.1:5000
Hello, world! (pid 23425)
[tedx@localhost ~]$ newrole -l s0-s0 -- -c "curl http://127.0.0.1:5000"
Password:
Hello, world! (pid 23476)
[tedx@localhost ~]$ newrole -l s1-s1 -- -c "curl http://127.0.0.1:5000"
Password:
Hello, world! (pid 23530)
[tedx@localhost ~]$ newrole -l s1-s1 -- -c "curl http://127.0.0.1:5000"
Password:
Hello, world! (pid 23530)

Notice that the last two requests went to the same child.

What’s missing you ask, policy! The dirty little secret of SELinux is policy which I’ve totally ignored in this example. If you were to create a unique SELinux type for your worker you’d probably do this by creating a script that wraps node and assign it a file context and you’d write policy to transition from the file context to your workers unique context. Luckily the webworker constructor can handle this by allowing you to specify the node command to use when running the worker script.

w = new Worker(path.join(__dirname, 'xworker.js'), {'path' : '/usr/local/bin/node-my-unique-selinux-type'});

Comments are closed.