Added Intel AMT session recording

Ylian Saint-Hilaire committed Aug 7, 2019 at 16:07 UTC 523bbf1bb7e4a3f5b7cf653ab9f321731432a271
4 files changed +85 -47
meshrelay.js
+28 -34
@@ -183,14 +183,8 @@ module.exports.CreateMeshRelay = function (parent, ws, req, domain, user, cookie
183 relayinfo.peer1.ws.send('c');
184 } else {
185 // Write the recording file header
186 - var firstBlock = Buffer.from(JSON.stringify({ magic: 'MeshCentralRelaySession', ver: 1, userid: sessionUser._id, username: sessionUser.name, sessionid: obj.id, ipaddr1: cleanRemoteAddr(ws._socket.remoteAddress), ipaddr2: cleanRemoteAddr(obj.peer.ws._socket.remoteAddress), time: new Date().toLocaleString(), protocol: req.query.p, nodeid: req.query.nodeid }));
187 - var header = Buffer.alloc(16); // Type (2) + Flags (2) + Size(4) + Time(8)
188 - header.writeInt16BE(1, 0); // Type (1 = Header, 2 = Network Data)
189 - header.writeInt16BE(0, 2); // Flags (1 = Binary, 2 = User)
190 - header.writeInt32BE(firstBlock.length, 4); // Size
191 - header.writeIntBE(ws.time, 10, 6); // Time
192 - var block = Buffer.concat([header, firstBlock]);
193 - parent.parent.fs.write(fd, block, 0, block.length, function (err, bytesWritten, buffer) {
186 + var firstBlock = JSON.stringify({ magic: 'MeshCentralRelaySession', ver: 1, userid: sessionUser._id, username: sessionUser.name, sessionid: obj.id, ipaddr1: cleanRemoteAddr(ws._socket.remoteAddress), ipaddr2: cleanRemoteAddr(obj.peer.ws._socket.remoteAddress), time: new Date().toLocaleString(), protocol: req.query.p, nodeid: req.query.nodeid });
187 + recordingEntry(fd, 2, ((req.query.browser) ? 2 : 0), firstBlock, function () {
188 relayinfo.peer1.ws.logfile = ws.logfile = { fd: fd, lock: false };
189 ws.send('c'); // Send connect to both peers
190 relayinfo.peer1.ws.send('c');
@@ -257,32 +251,7 @@ module.exports.CreateMeshRelay = function (parent, ws, req, domain, user, cookie
251 if (this.logfile != null) {
252 // Write data to log file then perform relay
253 var xthis = this;
260 - try {
261 - //console.log(obj);
262 - if (typeof data == 'string') {
263 - // String write
264 - var blockData = Buffer.from(data), header = Buffer.alloc(16); // Header: Type (2) + Flags (2) + Size(4) + Time(8)
265 - header.writeInt16BE(2, 0); // Type (1 = Header, 2 = Network Data)
266 - header.writeInt16BE(((req.query.browser) ? 2 : 0), 2); // Flags (1 = Binary, 2 = User)
267 - header.writeInt32BE(blockData.length, 4); // Size
268 - header.writeIntBE(new Date(), 10, 6); // Time
269 - var block = Buffer.concat([header, blockData]);
270 - parent.parent.fs.write(this.logfile.fd, block, 0, block.length, function (err, bytesWritten, buffer) {
271 - xthis.peer.send(data, ws.flushSink);
272 - });
273 - } else {
274 - // Binary write
275 - var header = Buffer.alloc(16); // Header: Type (2) + Flags (2) + Size(4) + Time(8)
276 - header.writeInt16BE(2, 0); // Type (1 = Header, 2 = Network Data)
277 - header.writeInt16BE(((req.query.browser) ? 3 : 1), 2); // Flags (1 = Binary, 2 = User)
278 - header.writeInt32BE(data.length, 4); // Size
279 - header.writeIntBE(new Date(), 10, 6); // Time
280 - var block = Buffer.concat([header, data]);
281 - parent.parent.fs.write(this.logfile.fd, block, 0, block.length, function (err, bytesWritten, buffer) {
282 - xthis.peer.send(data, ws.flushSink);
283 - });
284 - }
285 - } catch (ex) { console.log(ex); }
254 + recordingEntry(this.logfile.fd, 2, ((req.query.browser) ? 2 : 0), data, function () { xthis.peer.send(data, ws.flushSink); });
255 } else {
256 // Perform relay
257 this.peer.send(data, ws.flushSink);
@@ -355,6 +324,31 @@ module.exports.CreateMeshRelay = function (parent, ws, req, domain, user, cookie
324 delete obj.peer;
325 }
326
327 + // Record a new entry in a recording log
328 + function recordingEntry(fd, type, flags, data, func) {
329 + try {
330 + if (typeof data == 'string') {
331 + // String write
332 + var blockData = Buffer.from(data), header = Buffer.alloc(16); // Header: Type (2) + Flags (2) + Size(4) + Time(8)
333 + header.writeInt16BE(type, 0); // Type (1 = Header, 2 = Network Data)
334 + header.writeInt16BE(flags, 2); // Flags (1 = Binary, 2 = User)
335 + header.writeInt32BE(blockData.length, 4); // Size
336 + header.writeIntBE(new Date(), 10, 6); // Time
337 + var block = Buffer.concat([header, blockData]);
338 + parent.parent.fs.write(fd, block, 0, block.length, func);
339 + } else {
340 + // Binary write
341 + var header = Buffer.alloc(16); // Header: Type (2) + Flags (2) + Size(4) + Time(8)
342 + header.writeInt16BE(type, 0); // Type (1 = Header, 2 = Network Data)
343 + header.writeInt16BE(flags | 1, 2); // Flags (1 = Binary, 2 = User)
344 + header.writeInt32BE(data.length, 4); // Size
345 + header.writeIntBE(new Date(), 10, 6); // Time
346 + var block = Buffer.concat([header, data]);
347 + parent.parent.fs.write(fd, block, 0, block.length, func);
348 + }
349 + } catch (ex) { console.log(ex); func(); }
350 + }
351 +
352 // Mark this relay session as authenticated if this is the user end.
353 obj.authenticated = (user != null);
354 if (obj.authenticated) {
package.json
+1 -1
@@ -1,6 +1,6 @@
1 {
2 "name": "meshcentral",
3 - "version": "0.3.9-j",
3 + "version": "0.3.9-k",
4 "keywords": [
5 "Remote Management",
6 "Intel AMT",
sample-config.json
+2 -1
@@ -104,7 +104,8 @@
104 "_agentConfig": [ "webSocketMaskOverride=1" ],
105 "_SessionRecording": {
106 "_filepath": "C:\\temp",
107 - "protocols": [ 1, 2 ]
107 + "__protocols__": "Is an array: 1 = Terminal, 2 = Desktop, 5 = Files, 100 = Intel AMT WSMAN, 101 = Intel AMT Redirection",
108 + "protocols": [ 1, 2, 101 ]
109 }
110 },
111 "customer1": {
webserver.js
+54 -11
@@ -1960,10 +1960,9 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
1960 }
1961 }
1962
1963 - /*
1963 // Setup session recording if needed
1965 - if (domain.sessionrecording == true || ((typeof domain.sessionrecording == 'object') && ((domain.sessionrecording.protocols == null) || (domain.sessionrecording.protocols.indexOf(100) >= 0)))) { // TODO 100
1966 - var recFilename = 'relaysession' + ((domain.id == '') ? '' : '-') + domain.id + '-' + Date.now() + '-' + 'AAAAAAA' + '.mcrec'; // TODO: Random ID
1964 + if (domain.sessionrecording == true || ((typeof domain.sessionrecording == 'object') && ((domain.sessionrecording.protocols == null) || (domain.sessionrecording.protocols.indexOf((req.query.p == 2) ? 101 : 100) >= 0)))) { // TODO 100
1965 + var recFilename = 'relaysession' + ((domain.id == '') ? '' : '-') + domain.id + '-' + Date.now() + '-' + getRandomPassword() + '.mcrec'; // TODO: Random ID
1966 var recFullFilename = null;
1967 if (domain.sessionrecording.filepath) {
1968 try { obj.fs.mkdirSync(domain.sessionrecording.filepath); } catch (e) { }
@@ -1975,13 +1974,11 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
1974 var fd = obj.fs.openSync(recFullFilename, 'w');
1975 if (fd != null) {
1976 // Write the recording file header
1978 - //console.log({ magic: 'MeshCentralRelaySession', ver: 1, userid: user._id, username: user.name, ipaddr: cleanRemoteAddr(ws._socket.remoteAddress), nodeid: node._id, intelamt: true, protocol: parseInt(req.query.p), time: new Date().toLocaleString() });
1979 - var firstBlock = JSON.stringify({ magic: 'MeshCentralRelaySession', ver: 1, userid: user._id, username: user.name, ipaddr: cleanRemoteAddr(ws._socket.remoteAddress), nodeid: node._id, intelamt: true, protocol: parseInt(req.query.p), time: new Date().toLocaleString() })
1977 + var firstBlock = JSON.stringify({ magic: 'MeshCentralRelaySession', ver: 1, userid: user._id, username: user.name, ipaddr: cleanRemoteAddr(ws._socket.remoteAddress), nodeid: node._id, intelamt: true, protocol: (req.query.p == 2) ? 101 : 100, time: new Date().toLocaleString() })
1978 recordingEntry(fd, 1, 0, firstBlock, function () { });
1979 ws.logfile = { fd: fd, lock: false };
1980 }
1981 }
1984 - */
1982
1983 // If Intel AMT CIRA connection is available, use it
1984 if (((conn & 2) != 0) && (parent.mpsserver.ciraConnections[req.query.host] != null)) {
@@ -2001,7 +1998,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
1998 var ser = new SerialTunnel();
1999 var chnl = parent.mpsserver.SetupCiraChannel(ciraconn, port);
2000
2004 - // let's chain up the TLSSocket <-> SerialTunnel <-> CIRA APF (chnl)
2001 + // Let's chain up the TLSSocket <-> SerialTunnel <-> CIRA APF (chnl)
2002 // Anything that needs to be forwarded by SerialTunnel will be encapsulated by chnl write
2003 ser.forwardwrite = function (msg) {
2004 // TLS ---> CIRA
@@ -2056,7 +2053,16 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
2053 msg = msg.toString('binary');
2054 if (ws.interceptor) { msg = ws.interceptor.processBrowserData(msg); } // Run data thru interceptor
2055 //console.log('WS --> AMT', Buffer.from(msg, 'binary').toString('hex'));
2059 - if (ws.forwardclient.xtls == 1) { ws.forwardclient.write(Buffer.from(msg, 'binary')); } else { ws.forwardclient.write(msg); }
2056 +
2057 + // Log to recording file
2058 + if (ws.logfile == null) {
2059 + // Forward data to the associated TCP connection.
2060 + if (ws.forwardclient.xtls == 1) { ws.forwardclient.write(Buffer.from(msg, 'binary')); } else { ws.forwardclient.write(msg); }
2061 + } else {
2062 + // Log to recording file
2063 + var msg2 = Buffer.from(msg, 'binary');
2064 + recordingEntry(ws.logfile.fd, 2, 2, msg2, function () { try { if (ws.forwardclient.xtls == 1) { ws.forwardclient.write(msg2); } else { ws.forwardclient.write(msg); } } catch (ex) { } });
2065 + }
2066 });
2067
2068 // If error, close the associated TCP connection.
@@ -2064,12 +2070,18 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
2070 console.log('CIRA server websocket error from ' + ws._socket.remoteAddress + ', ' + err.toString().split('\r')[0] + '.');
2071 Debug(1, 'Websocket relay closed on error.');
2072 if (ws.forwardclient && ws.forwardclient.close) { ws.forwardclient.close(); } // TODO: If TLS is used, we need to close the socket that is wrapped by TLS
2073 +
2074 + // Close the recording file
2075 + if (ws.logfile != null) { obj.fs.close(ws.logfile.fd); ws.logfile = null; }
2076 });
2077
2078 // If the web socket is closed, close the associated TCP connection.
2079 ws.on('close', function (req) {
2080 Debug(1, 'Websocket relay closed.');
2081 if (ws.forwardclient && ws.forwardclient.close) { ws.forwardclient.close(); } // TODO: If TLS is used, we need to close the socket that is wrapped by TLS
2082 +
2083 + // Close the recording file
2084 + if (ws.logfile != null) { obj.fs.close(ws.logfile.fd); ws.logfile = null; }
2085 });
2086
2087 ws.forwardclient.onStateChange = function (ciraconn, state) {
@@ -2081,7 +2093,15 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
2093 Debug(4, 'Relay CIRA data', data.length);
2094 if (ws.interceptor) { data = ws.interceptor.processAmtData(data); } // Run data thru interceptor
2095 //console.log('AMT --> WS', Buffer.from(data, 'binary').toString('hex'));
2084 - if (data.length > 0) { try { ws.send(Buffer.from(data, 'binary')); } catch (e) { } } // TODO: Add TLS support
2096 + if (data.length > 0) {
2097 + if (ws.logfile == null) {
2098 + try { ws.send(Buffer.from(data, 'binary')); } catch (e) { } // TODO: Add TLS support
2099 + } else {
2100 + // Log to recording file
2101 + data = Buffer.from(data, 'binary');
2102 + recordingEntry(ws.logfile.fd, 2, 2, data, function () { try { ws.send(data); } catch (e) { } }); // TODO: Add TLS support
2103 + }
2104 + }
2105 };
2106
2107 ws.forwardclient.onSendOk = function (ciraconn) {
@@ -2116,7 +2136,16 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
2136 }
2137 msg = msg.toString('binary');
2138 if (ws.interceptor) { msg = ws.interceptor.processBrowserData(msg); } // Run data thru interceptor
2119 - ws.forwardclient.write(Buffer.from(msg, 'binary')); // Forward data to the associated TCP connection.
2139 +
2140 + // Log to recording file
2141 + if (ws.logfile == null) {
2142 + // Forward data to the associated TCP connection.
2143 + try { ws.forwardclient.write(Buffer.from(msg, 'binary')); } catch (ex) { }
2144 + } else {
2145 + // Log to recording file
2146 + msg = Buffer.from(msg, 'binary');
2147 + recordingEntry(ws.logfile.fd, 2, 2, msg, function () { try { ws.forwardclient.write(msg); } catch (ex) { } });
2148 + }
2149 });
2150
2151 // If error, close the associated TCP connection.
@@ -2124,12 +2153,18 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
2153 console.log('Error with relay web socket connection from ' + ws._socket.remoteAddress + ', ' + err.toString().split('\r')[0] + '.');
2154 Debug(1, 'Error with relay web socket connection from ' + ws._socket.remoteAddress + '.');
2155 if (ws.forwardclient) { try { ws.forwardclient.destroy(); } catch (e) { } }
2156 +
2157 + // Close the recording file
2158 + if (ws.logfile != null) { obj.fs.close(ws.logfile.fd); ws.logfile = null; }
2159 });
2160
2161 // If the web socket is closed, close the associated TCP connection.
2162 ws.on('close', function () {
2163 Debug(1, 'Closing relay web socket connection to ' + req.query.host + '.');
2164 if (ws.forwardclient) { try { ws.forwardclient.destroy(); } catch (e) { } }
2165 +
2166 + // Close the recording file
2167 + if (ws.logfile != null) { obj.fs.close(ws.logfile.fd); ws.logfile = null; }
2168 });
2169
2170 // Compute target port
@@ -2165,7 +2200,14 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
2200 if (obj.parent.debugLevel >= 4) { Debug(4, ' ' + Buffer.from(data, 'binary').toString('hex')); }
2201 }
2202 if (ws.interceptor) { data = ws.interceptor.processAmtData(data); } // Run data thru interceptor
2168 - try { ws.send(Buffer.from(data, 'binary')); } catch (e) { }
2203 + if (ws.logfile == null) {
2204 + // No logging
2205 + try { ws.send(Buffer.from(data, 'binary')); } catch (e) { }
2206 + } else {
2207 + // Log to recording file
2208 + data = Buffer.from(data, 'binary');
2209 + recordingEntry(ws.logfile.fd, 2, 0, data, function () { try { ws.send(data); } catch (e) { } });
2210 + }
2211 });
2212
2213 // If the TCP connection closes, disconnect the associated web socket.
@@ -3412,6 +3454,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
3454 // Generate a random Intel AMT password
3455 function checkAmtPassword(p) { return (p.length > 7) && (/\d/.test(p)) && (/[a-z]/.test(p)) && (/[A-Z]/.test(p)) && (/\W/.test(p)); }
3456 function getRandomAmtPassword() { var p; do { p = Buffer.from(obj.crypto.randomBytes(9), 'binary').toString('base64').split('/').join('@'); } while (checkAmtPassword(p) == false); return p; }
3457 + function getRandomPassword() { return Buffer.from(obj.crypto.randomBytes(9), 'binary').toString('base64').split('/').join('@'); }
3458
3459 // Clean a IPv6 address that encodes a IPv4 address
3460 function cleanRemoteAddr(addr) { if (addr.startsWith('::ffff:')) { return addr.substring(7); } else { return addr; } }