syslog-client-node-tls
Version:
TCP and UDP syslog client RFC 5424 & RFC 3164
920 lines (831 loc) • 24.2 kB
JavaScript
var chai = require("chai"),
expect = chai.expect,
assert = chai.assert,
fs = require("fs"),
path = require("path"),
net = require("net"),
tls = require("tls"),
syslogClient = require("../index.js"),
syslogUdpPort = 5514,
syslogTcpPort = 5514,
syslogTlsPort = 6514,
dgram = require("dgram"),
rl = require("readline"),
os = require("os"),
Promise = require("bluebird"),
queuedSyslogUdpMessages = [],
pendingSyslogUdpPromises = [],
queuedSyslogTcpMessages = [],
pendingSyslogTcpPromises = [],
queuedSyslogTlsMessages = [],
pendingSyslogTlsPromises = [];
var tlsPrivateKey = fs.readFileSync(path.join(__dirname, "key.pem"), "utf8");
var tlsCertificate = fs.readFileSync(path.join(__dirname, "certificate.pem"), "utf8");
var wrongCertificate = fs.readFileSync(path.join(__dirname, "wrong.pem"), "utf8");
chai.should();
function awaitSyslogUdpMsg() {
return new Promise(function (resolve, reject) {
var queued = queuedSyslogUdpMessages.shift();
if (queued)
return resolve(queued);
pendingSyslogUdpPromises.push(resolve);
});
}
function awaitSyslogTcpMsg() {
return new Promise(function (resolve, reject) {
var queued = queuedSyslogTcpMessages.shift();
if (queued)
return resolve(queued);
pendingSyslogTcpPromises.push(resolve);
});
}
function awaitSyslogTlsMsg() {
return new Promise(function (resolve, reject) {
var queued = queuedSyslogTlsMessages.shift();
if (queued)
return resolve(queued);
pendingSyslogTlsPromises.push(resolve);
});
}
function escapeRegExp(string) {
return (""+string).replace(/[\\^$.*+?()[\]{}|]/g, '\\$&');
}
function constructSyslogRegex(pri, hostname, msg, timestamp) {
var pat_date;
if (typeof timestamp === 'undefined') {
pat_date = "\\w+\\s{1,2}\\d{1,2} \\d{2}:\\d{2}:\\d{2}";
} else {
var elems = timestamp.toString().split(/\s+/);
var month = elems[1];
var day = elems[2];
var time = elems[4];
if (day[0] === "0")
day = " " + day.substr(1, 1);
pat_date = escapeRegExp(month + " " + day + " " + time);
}
return new RegExp(
"^<"+
escapeRegExp(pri)+
">"+
pat_date+
" "+
escapeRegExp(hostname)+
" "+
escapeRegExp(msg)+
"\\n?$"
);
}
function constructRfc5424Regex(pri, hostname, msg, msgid, timestamp) {
var pat_date = typeof timestamp === 'undefined' ?
"\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}(\.\\d+)Z" :
escapeRegExp(timestamp.toISOString());
return new RegExp(
"^<"+
escapeRegExp(pri)+
">\\d+ "+
pat_date+
" "+
escapeRegExp(hostname)+
" \\S{1,48} \\d+ "+
escapeRegExp(msgid)+
" - "+
escapeRegExp(msg)+
"\\n?$"
);
}
var udpServer = dgram.createSocket("udp4"),
tcpServer,
tlsServer;
before(function (_done) {
var count = 3;
var done = function () {
count--;
if (count === 0)
_done();
};
udpServer.on("message", function (msg, rinfo) {
var pend = pendingSyslogUdpPromises.shift();
if (pend)
return pend(msg.toString());
queuedSyslogUdpMessages.push(msg.toString());
});
udpServer.on("listening", function () {
console.log("Started UDP syslog server");
done();
});
udpServer.on("error", function (err) {
throw new Error(err);
});
udpServer.bind(syslogUdpPort);
tcpServer = net.createServer(function (socket) {
var lines = rl.createInterface(socket, socket);
lines.on("line", function (line) {
var pend = pendingSyslogTcpPromises.shift();
if (pend)
return pend(line);
queuedSyslogTcpMessages.push(line);
});
});
tcpServer.on("error", function (err) {
throw new Error(err);
});
tcpServer.listen(syslogTcpPort, function () {
console.log("Started TCP syslog server");
done();
});
tlsServer = tls.createServer({
key: tlsPrivateKey,
cert: tlsCertificate,
secureProtocol: "TLSv1_2_method"
}, function (socket) {
var lines = rl.createInterface(socket, socket);
lines.on("line", function (line) {
var pend = pendingSyslogTlsPromises.shift();
if (pend)
return pend(line);
queuedSyslogTlsMessages.push(line);
});
});
tlsServer.on("error", function (err) {
throw new Error(err);
});
tlsServer.listen(syslogTlsPort, function () {
console.log("Started TLS syslog server");
done();
});
});
after(function (done) {
udpServer.close();
tlsServer.close();
tcpServer.close();
done();
});
describe("Syslog Client", function () {
// eslint-disable-next-line max-statements
it("should set options correctly with defaults", function (done) {
var client;
client = new syslogClient.createClient();
client.target.should.equal("127.0.0.1");
client.port.should.equal(514);
client.syslogHostname.should.equal(os.hostname());
client.tcpTimeout.should.equal(10000);
client.transport.should.equal(syslogClient.Transport.Udp);
client = new syslogClient.createClient("127.0.0.2");
client.target.should.equal("127.0.0.2");
client.port.should.equal(514);
client.syslogHostname.should.equal(os.hostname());
client.tcpTimeout.should.equal(10000);
client.transport.should.equal(syslogClient.Transport.Udp);
client = new syslogClient.createClient("127.0.0.2", {});
client.target.should.equal("127.0.0.2");
client.port.should.equal(514);
client.syslogHostname.should.equal(os.hostname());
client.tcpTimeout.should.equal(10000);
client.transport.should.equal(syslogClient.Transport.Udp);
client = new syslogClient.createClient("127.0.0.2", {
syslogHostname: "test"
});
client.target.should.equal("127.0.0.2");
client.port.should.equal(514);
client.syslogHostname.should.equal("test");
client.tcpTimeout.should.equal(10000);
client.transport.should.equal(syslogClient.Transport.Udp);
client = new syslogClient.createClient("127.0.0.2", {
syslogHostname: "test",
port: 5555
});
client.target.should.equal("127.0.0.2");
client.port.should.equal(5555);
client.syslogHostname.should.equal("test");
client.tcpTimeout.should.equal(10000);
client.transport.should.equal(syslogClient.Transport.Udp);
client = new syslogClient.createClient("127.0.0.2", {
syslogHostname: "test",
port: 5555,
tcpTimeout: 50
});
client.target.should.equal("127.0.0.2");
client.port.should.equal(5555);
client.syslogHostname.should.equal("test");
client.tcpTimeout.should.equal(50);
client.transport.should.equal(syslogClient.Transport.Udp);
client = new syslogClient.createClient("127.0.0.2", {
syslogHostname: "test",
port: 5555,
tcpTimeout: 50,
transport: syslogClient.Transport.Tcp
});
client.target.should.equal("127.0.0.2");
client.port.should.equal(5555);
client.syslogHostname.should.equal("test");
client.tcpTimeout.should.equal(50);
client.transport.should.equal(syslogClient.Transport.Tcp);
client = new syslogClient.createClient("127.0.0.2", {
syslogHostname: "test",
port: 5555,
tcpTimeout: 50,
transport: syslogClient.Transport.Tls,
tlsCA: tlsCertificate
});
client.target.should.equal("127.0.0.2");
client.port.should.equal(5555);
client.syslogHostname.should.equal("test");
client.tcpTimeout.should.equal(50);
client.transport.should.equal(syslogClient.Transport.Tls);
client.tlsCA.should.equal(tlsCertificate);
client = new syslogClient.createClient("127.0.0.2", {
syslogHostname: "test",
port: 5555,
tcpTimeout: 50,
transport: "Not a valid transport"
});
client.target.should.equal("127.0.0.2");
client.port.should.equal(5555);
client.syslogHostname.should.equal("test");
client.tcpTimeout.should.equal(50);
client.transport.should.equal(syslogClient.Transport.Udp);
done();
});
it("should connect to UDP and send log(s)", function () {
var hostname = "testhostname";
var client = new syslogClient.createClient("127.0.0.1", {
port: syslogUdpPort,
syslogHostname: hostname,
transport: syslogClient.Transport.Udp
});
client.log("This is a test");
return awaitSyslogUdpMsg()
.then(function (msg) {
assert.match(msg, constructSyslogRegex(9, hostname, "This is a test"));
client.log("This is a second test");
return awaitSyslogUdpMsg();
})
.then(function (msg) {
assert.match(msg, constructSyslogRegex(9, hostname, "This is a second test"));
client.close();
})
});
it("should bind UDP socket to specific network address and send log(s)", function () {
var hostname = "testhostname";
var client = new syslogClient.createClient("127.0.0.1", {
port: syslogUdpPort,
syslogHostname: hostname,
transport: syslogClient.Transport.Udp,
udpBindAddress: "127.0.0.1"
});
client.log("This is a test");
return awaitSyslogUdpMsg()
.then(function (msg) {
assert.match(msg, constructSyslogRegex(9, hostname, "This is a test"));
client.log("This is a second test");
return awaitSyslogUdpMsg();
})
.then(function (msg) {
assert.match(msg, constructSyslogRegex(9, hostname, "This is a second test"));
client.close();
});
});
it("should fail when binding UDP socket to unknown network address", function (done) {
var hostname = "testhostname";
var client = new syslogClient.createClient("127.0.0.1", {
port: syslogUdpPort,
syslogHostname: hostname,
transport: syslogClient.Transport.Udp,
udpBindAddress: "500.500.500.500" // invalid IP
});
var count = 2,
decFn = function () {
count--;
if (count === 0) {
client.close();
done();
}
};
client.on("error", function (err) {
err.should.be.instanceof(Error);
if (decFn)
decFn();
});
client.log("shouldn't work", function (err) {
err.should.be.instanceof(Error);
decFn();
});
});
it("should connect to TCP and send log(s)", function () {
var hostname = "testhostname";
var client = new syslogClient.createClient("127.0.0.1", {
port: syslogTcpPort,
syslogHostname: hostname,
transport: syslogClient.Transport.Tcp
});
client.log("This is a test");
return awaitSyslogTcpMsg()
.then(function (msg) {
assert.match(msg, constructSyslogRegex(9, hostname, "This is a test"));
client.log("This is a second test");
return awaitSyslogTcpMsg();
})
.then(function (msg) {
assert.match(msg, constructSyslogRegex(9, hostname, "This is a second test"));
client.close();
});
});
it("should connect to TCP with TLS and send log(s)", function () {
var hostname = "testhostname";
var client = new syslogClient.createClient("localhost", {
port: syslogTlsPort,
syslogHostname: hostname,
transport: syslogClient.Transport.Tls,
tlsCA: tlsCertificate
});
client.log("This is a test");
return awaitSyslogTlsMsg()
.then(function (msg) {
assert.match(msg, constructSyslogRegex(9, hostname, "This is a test"));
client.log("This is a second test");
return awaitSyslogTlsMsg();
})
.then(function (msg) {
assert.match(msg, constructSyslogRegex(9, hostname, "This is a second test"));
client.close();
});
});
it("should reuse the UDP transport", function () {
var hostname = "testhostname";
var client = new syslogClient.createClient("127.0.0.1", {
port: syslogUdpPort,
syslogHostname: hostname,
transport: syslogClient.Transport.Udp
});
client.log("Transport reuse test");
var transport_;
return awaitSyslogUdpMsg()
.then(function (msg) {
transport_ = client.transport_;
client.log("Transport reuse test 2");
assert.typeOf(transport_, "object");
return awaitSyslogUdpMsg();
})
.then(function (msg) {
assert.equal(transport_, client.transport_);
client.close();
});
});
it("should reuse the TCP transport", function () {
var hostname = "testhostname";
var client = new syslogClient.createClient("127.0.0.1", {
port: syslogTcpPort,
syslogHostname: hostname,
transport: syslogClient.Transport.Tcp
});
client.log("Transport reuse test");
var transport_;
return awaitSyslogTcpMsg()
.then(function (msg) {
transport_ = client.transport_;
assert.typeOf(transport_, "object");
client.log("Transport reuse test 2");
return awaitSyslogTcpMsg();
})
.then(function (msg) {
assert.equal(transport_, client.transport_);
client.close();
});
});
it("should reuse the TLS transport", function () {
var hostname = "testhostname";
var client = new syslogClient.createClient("localhost", {
port: syslogTlsPort,
syslogHostname: hostname,
transport: syslogClient.Transport.Tls,
tlsCA: tlsCertificate
});
client.log("Transport reuse test");
var transport_;
return awaitSyslogTlsMsg()
.then(function (msg) {
transport_ = client.transport_;
assert.typeOf(transport_, "object");
client.log("Transport reuse test 2");
return awaitSyslogTlsMsg();
})
.then(function (msg) {
assert.equal(transport_, client.transport_);
client.close();
});
});
it("should call close event when closed UDP", function (done) {
var hostname = "testhostname";
var client = new syslogClient.createClient("127.0.0.1", {
port: syslogUdpPort,
syslogHostname: hostname,
transport: syslogClient.Transport.Udp
});
client.log("Transport close test");
client.once("close", function () {
assert.equal(client.transport_, undefined);
client.once("close", function () {
assert.equal(client.transport_, undefined);
done();
});
client.close();
});
awaitSyslogUdpMsg()
.then(function (msg) {
client.close();
})
});
it("should call close event when closed TCP", function (done) {
var hostname = "testhostname";
var client = new syslogClient.createClient("127.0.0.1", {
port: syslogTcpPort,
syslogHostname: hostname,
transport: syslogClient.Transport.Tcp
});
client.log("Transport close test");
client.once("close", function () {
assert.equal(client.transport_, undefined);
client.once("close", function () {
assert.equal(client.transport_, undefined);
done();
});
client.close();
});
awaitSyslogTcpMsg()
.then(function (msg) {
client.close();
})
});
it("should call close event when closed TLS", function (done) {
var hostname = "testhostname";
var client = new syslogClient.createClient("localhost", {
port: syslogTlsPort,
syslogHostname: hostname,
transport: syslogClient.Transport.Tls,
tlsCA: tlsCertificate
});
client.log("Transport close test");
client.once("close", function () {
assert.equal(client.transport_, undefined);
client.once("close", function () {
assert.equal(client.transport_, undefined);
done();
});
client.close();
});
awaitSyslogTlsMsg()
.then(function (msg) {
client.close();
})
});
it("should reconnect after connection is closed UDP", function (done) {
var hostname = "testhostname";
var client = new syslogClient.createClient("127.0.0.1", {
port: syslogUdpPort,
syslogHostname: hostname,
transport: syslogClient.Transport.Udp
});
client.log("Transport close test");
client.once("close", function () {
client.log("Restart connection test");
awaitSyslogUdpMsg()
.then(function (msg) {
assert.match(msg, constructSyslogRegex(9, hostname,
"Restart connection test"));
client.close();
done();
})
});
awaitSyslogUdpMsg()
.then(function (msg) {
client.close();
})
});
it("should reconnect after connection is closed TCP", function (done) {
var hostname = "testhostname";
var client = new syslogClient.createClient("127.0.0.1", {
port: syslogTcpPort,
syslogHostname: hostname,
transport: syslogClient.Transport.Tcp
});
client.log("Transport close test");
client.once("close", function () {
client.log("Restart connection test");
awaitSyslogTcpMsg()
.then(function (msg) {
assert.match(msg, constructSyslogRegex(9, hostname,
"Restart connection test"));
client.close();
done();
})
});
awaitSyslogTcpMsg()
.then(function (msg) {
client.close();
})
});
it("should reconnect after connection is closed TLS", function (done) {
var hostname = "testhostname";
var client = new syslogClient.createClient("localhost", {
port: syslogTlsPort,
syslogHostname: hostname,
transport: syslogClient.Transport.Tls,
tlsCA: tlsCertificate
});
client.log("Transport close test");
client.once("close", function () {
client.log("Restart connection test");
awaitSyslogTlsMsg()
.then(function (msg) {
assert.match(msg, constructSyslogRegex(9, hostname,
"Restart connection test"));
client.close();
done();
})
});
awaitSyslogTlsMsg()
.then(function (msg) {
client.close();
})
});
it("should throw if a string isnt provided to .log()", function () {
var hostname = "testhostname";
var client = new syslogClient.createClient("127.0.0.1", {
port: syslogTcpPort,
syslogHostname: hostname,
transport: syslogClient.Transport.Tcp
});
(function () {
client.log();
}).should.throw(Error);
(function () {
client.log({});
}).should.throw(Error);
(function () {
client.log([]);
}).should.throw(Error);
(function () {
client.log(undefined);
}).should.throw(Error);
(function () {
client.log(null);
}).should.throw(Error);
});
it("should take a callback as the second argument to .log", function (done) {
var hostname = "testhostname";
var client = new syslogClient.createClient("127.0.0.1", {
port: syslogTcpPort,
syslogHostname: hostname,
transport: syslogClient.Transport.Tcp
});
var count = 2,
decFn = function () {
count--;
if (count === 0) {
client.close();
done();
}
};
client.log("anything", decFn);
awaitSyslogTcpMsg().then(decFn)
});
it("should take options as the second argument to .log", function (done) {
var hostname = "testhostname";
var client = new syslogClient.createClient("127.0.0.1", {
port: syslogTcpPort,
syslogHostname: hostname,
transport: syslogClient.Transport.Tcp
});
var count = 2,
decFn = function () {
count--;
if (count === 0) {
client.close();
done();
}
};
client.log("anything", {
facility: syslogClient.Facility.System,
severity: syslogClient.Severity.Notice
}, decFn);
awaitSyslogTcpMsg().then(function (msg) {
assert.match(msg, constructSyslogRegex(29, hostname,
"anything"));
decFn();
});
});
it("should correctly calculate the PRI value", function (done) {
var hostname = "testhostname";
var client = new syslogClient.createClient("127.0.0.1", {
port: syslogTcpPort,
syslogHostname: hostname,
transport: syslogClient.Transport.Tcp
});
var count = 2,
decFn = function () {
count--;
if (count === 0) {
client.close();
done();
}
};
client.log("anything", {
facility: syslogClient.Facility.Local0,
severity: syslogClient.Severity.Emergency
}, decFn);
awaitSyslogTcpMsg().then(function (msg) {
assert.match(msg, constructSyslogRegex(128, hostname,
"anything"));
decFn();
});
})
it("should call on error on connection error Tcp when invalid port", function (done) {
var hostname = "testhostname";
var client = new syslogClient.createClient("127.0.0.1", {
port: 502342323, // hopefully this isnt in use, TODO find free ports for testing
tcpTimeout: 2000,
syslogHostname: hostname,
transport: syslogClient.Transport.Tcp
});
var count = 2,
decFn = function () {
count--;
if (count === 0) {
client.close();
done();
}
};
client.on("error", function (err) {
err.should.be.instanceof(Error);
decFn();
});
client.log("shouldn't work", function (err) {
err.should.be.instanceof(Error);
decFn();
});
});
it("should call on error on connection error TLS when invalid port", function (done) {
var hostname = "testhostname";
var client = new syslogClient.createClient("localhost", {
port: 512342317, // hopefully this isnt in use, TODO find free ports for testing
tcpTimeout: 2000,
syslogHostname: hostname,
transport: syslogClient.Transport.Tls,
tlsCA: tlsCertificate
});
var count = 2,
decFn = function () {
count--;
if (count === 0)
done();
};
client.on("error", function (err) {
err.should.be.instanceof(Error);
decFn();
});
client.log("shouldn't work", function (err) {
err.should.be.instanceof(Error);
decFn();
});
});
it("should call on error on connection error Udp when invalid port", function (done) {
var hostname = "testhostname";
var client = new syslogClient.createClient("127.0.0.1", {
port: 12378726362, // hopefully this isnt in use, TODO find free ports for testing
syslogHostname: hostname,
transport: syslogClient.Transport.Udp
});
var count = 2,
decFn = function () {
count--;
if (count === 0)
done();
};
client.on("error", function (err) {
expect(err).to.be.instanceof(Error);
decFn();
});
client.log("shouldn't work", function (err) {
err.should.be.instanceof(Error);
decFn();
});
});
it("should call on error with timeout on connection error Tcp", function (done) {
var hostname = "testhostname";
var client = new syslogClient.createClient("203.0.113.1", {
port: syslogTcpPort, // hopefully this isnt in use, TODO find free ports for testing
tcpTimeout: 500,
syslogHostname: hostname,
transport: syslogClient.Transport.Tcp
});
var count = 2,
decFn = function () {
count--;
if (count === 0)
done();
};
client.on("error", function (err) {
err.should.be.instanceof(Error);
decFn();
});
client.log("shouldn't work", function (err) {
err.should.be.instanceof(Error);
decFn();
});
});
it("should call on error with timeout on connection error TLS", function (done) {
var hostname = "testhostname";
var client = new syslogClient.createClient("203.0.113.1", {
port: syslogTlsPort,
tcpTimeout: 500,
syslogHostname: hostname,
transport: syslogClient.Transport.Tls,
tlsCA: tlsCertificate
});
var count = 2,
decFn = function () {
count--;
if (count === 0)
done();
};
client.on("error", function (err) {
err.should.be.instanceof(Error);
decFn();
});
client.log("shouldn't work", function (err) {
err.should.be.instanceof(Error);
decFn();
});
});
it("should connect to UDP and send back-dated obsolete RFC 3164 log(s)", function () {
var hostname = "testhostname";
var client = new syslogClient.createClient("127.0.0.1", {
port: syslogUdpPort,
syslogHostname: hostname,
transport: syslogClient.Transport.Udp
});
var backdate = new Date(2017, 2, 1);
client.log("This is a test", {
msgid: 98765, timestamp: backdate
});
return awaitSyslogUdpMsg()
.then(function (msg) {
assert.match(msg, constructSyslogRegex(9, hostname, "This is a test", backdate));
client.log("This is a second test", {
rfc3164: true, msgid: 102938
});
return awaitSyslogUdpMsg();
})
.then(function (msg) {
assert.match(msg, constructSyslogRegex(9, hostname, "This is a second test"));
client.close();
});
});
it("should connect to UDP and send back-dated RFC 5424 log(s)", function () {
var hostname = "testhostname";
var client = new syslogClient.createClient("127.0.0.1", {
port: syslogUdpPort,
syslogHostname: hostname,
transport: syslogClient.Transport.Udp
});
var backdate = new Date(2017, 2, 1);
client.log("This is a test", {
rfc3164: false, msgid: 98765, timestamp: backdate
});
return awaitSyslogUdpMsg()
.then(function (msg) {
assert.match(msg, constructRfc5424Regex(9, hostname, "This is a test", 98765, backdate));
client.log("This is a second test", {
rfc3164: false
});
return awaitSyslogUdpMsg();
})
.then(function (msg) {
assert.match(msg, constructRfc5424Regex(9, hostname, "This is a second test", "-"));
client.close();
});
});
it("should refuse to connect to TLS with invalid certificate", function (done) {
var hostname = "testhostname";
var client = new syslogClient.createClient("localhost", {
port: syslogTlsPort,
syslogHostname: hostname,
transport: syslogClient.Transport.Tls,
tlsCA: wrongCertificate
});
var count = 2,
decFn = function () {
count--;
if (count === 0)
done();
};
client.on("error", function (err) {
err.should.be.instanceof(Error);
decFn();
});
client.log("shouldn't work", function (err) {
err.should.be.instanceof(Error);
decFn();
});
});
});