Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added "keyagents" domain config option. #6190

Draft
wants to merge 4 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -339,6 +339,9 @@
"isaml",
"Jitsi",
"jumpcloud",
"keyagents",
"keyagentsgrace",
"keyagenttimeout",
"keyfile",
"keygrip",
"keyid",
Expand Down
43 changes: 42 additions & 1 deletion meshagent.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
"use strict";

// Construct a MeshAgent object, called upon connection
module.exports.CreateMeshAgent = function (parent, db, ws, req, args, domain) {
module.exports.CreateMeshAgent = function (parent, db, ws, req, args, domain, key) {
const forge = parent.parent.certificateOperations.forge;
const common = parent.parent.common;
parent.agentStats.createMeshAgentCount++;
Expand All @@ -29,6 +29,7 @@ module.exports.CreateMeshAgent = function (parent, db, ws, req, args, domain) {
obj.remoteaddr = req.clientIp;
obj.remoteaddrport = obj.remoteaddr + ':' + ws._socket.remotePort;
obj.nonce = parent.crypto.randomBytes(48).toString('binary');
obj.key = key
//ws._socket.setKeepAlive(true, 240000); // Set TCP keep alive, 4 minutes
if (args.agentidletimeout != 0) { ws._socket.setTimeout(args.agentidletimeout, function () { obj.close(1); }); } // Inactivity timeout of 2:30 minutes, by default agent will WebSocket ping every 2 minutes and server will pong back.
//obj.nodeid = null;
Expand Down Expand Up @@ -519,6 +520,19 @@ module.exports.CreateMeshAgent = function (parent, db, ws, req, args, domain) {

// Check the agent signature if we can
if (obj.agentnonce == null) { obj.unauthsign = msg.substring(4 + certlen); } else {
if (obj.key) {
let _nodekey = 'node/' + domain.id + '/' + obj.unauth.nodeid;
if (!obj.key.nodeid && (!obj.key.expire || (+Date.now() < obj.key.expire))) {
obj.key.nodeid = _nodekey;
obj.key.domain = domain.id
db.Set(obj.key)
} else if (obj.key.nodeid !== _nodekey) {
parent.agentStats.agentBadSignature3Count++;
parent.setAgentIssue(obj, "BadSignature3");
parent.parent.debug('agent', 'Agent connected as a node that does not match its key, holding connection (' + obj.remoteaddrport + ').');
console.log('Agent connected as a node that does not match its key, holding connection (' + obj.remoteaddrport + ').'); return;
}
}
if (processAgentSignature(msg.substring(4 + certlen)) == false) {
parent.agentStats.agentBadSignature2Count++;
parent.setAgentIssue(obj, "BadSignature2");
Expand Down Expand Up @@ -1107,6 +1121,33 @@ module.exports.CreateMeshAgent = function (parent, db, ws, req, args, domain) {
if (domain.agentselfguestsharing) { serverInfo.agentSelfGuestSharing = true; }
obj.send(JSON.stringify(serverInfo));

if ((domain.keyagents || parent.parent.config.settings.keyagents) && obj.key == undefined) {
let keyagentsgrace = domain.keyagentsgrace || parent.parent.config.settings.keyagentsgrace || 0;
if (keyagentsgrace !== 0) {
keyagentsgrace = +(Date.parse(keyagentsgrace))
}
// If keyagentsgrace is not right here, we would have errored out long before this, but might as well handle it here as well.
if (+(new Date()) < keyagentsgrace) {
if (args.lanonly != true) {
let serverName = parent.getWebServerName(domain, req);
let httpsPort = ((args.aliasport == null) ? args.port : args.aliasport); // Use HTTPS alias port is specified
let xdomain = (domain.dns == null) ? domain.id : '';
if (xdomain != '') xdomain += '/';
let connectString = 'wss://' + serverName + ':' + httpsPort + '/' + xdomain + 'agent.ashx';
let [_agentKey, _key] = parent.generateAgentKey(domain)
_agentKey.nodeid = obj.dbNodeKey
db.Set(_agentKey);
connectString += `?key=${_key.toString("hex")}`;
obj.key = _agentKey
obj.send(JSON.stringify({ action: 'msg', type: 'console', value: `msh set MeshServer ${connectString}`,
// msg expects a session ID, but we don't have one and msh command doesn't use it, so spoof it
sessionid: parent.crypto.randomBytes(16).toString("hex"),
// Force all rights
rights: 4294967295 }));
}
}
}

// Plug in handler
if (parent.parent.pluginHandler != null) {
parent.parent.pluginHandler.callHook('hook_agentCoreIsStable', obj, parent);
Expand Down
30 changes: 30 additions & 0 deletions meshcentral-config-schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -383,6 +383,21 @@
"default": false,
"description": "When enabled, MeshCentral will block all downloads of MeshAgent including install scripts, if the user is not logged in"
},
"keyAgents": {
"type": "boolean",
"default": false,
"description": "When enabled, Meshcentral will key each agent downloaded to a specific node id, which is determined on first agent connection. If an agent attempts to connect without this key, or with a node id that does not match this key, it will be ignored. Deleting a device will remove its key, preventing that agent from connecting again in the future."
},
"keyAgentsGrace": {
"type": "string",
"default": null,
"description": "Date to allow unkeyed connections. When set in conjunction with keyAgents, meshcentral will accept any agent connection until the date specified and attempt to automatically update the agent to a keyed agent. Standard javascript date format, described at https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date#date_time_string_format ."
},
"keyAgentTimeout": {
"type": "integer",
"default": 60,
"description": "Timeout in minutes for which an agentKey will be valid. If no device connects by this time, that agentkey will be deactivated. If set to null, keys will never timeout."
},
"ignoreAgentHashCheck": {
"type": [
"boolean",
Expand Down Expand Up @@ -1742,6 +1757,21 @@
"default": false,
"description": "When enabled, MeshCentral will block all downloads of MeshAgent including install scripts, if the user is not logged in"
},
"keyAgents": {
"type": "boolean",
"default": false,
"description": "When enabled, Meshcentral will key each agent downloaded to a specific node id, which is determined on first agent connection. If an agent attempts to connect without this key, or with a node id that does not match this key, it will be ignored. Deleting a device will remove its key, preventing that agent from connecting again in the future."
},
"keyAgentsGrace": {
"type": "string",
"default": null,
"description": "Date to allow unkeyed connections. When set in conjunction with keyAgents, meshcentral will accept any agent connection until the date specified and attempt to automatically update the agent to a keyed agent. Standard javascript date format, described at https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date#date_time_string_format ."
},
"keyAgentTimeout": {
"type": "integer",
"default": 60,
"description": "Timeout in minutes for which an agentKey will be valid. If no device connects by this time, that agentkey will be deactivated. If set to null, keys will never timeout."
},
"geoLocation": {
"type": "boolean",
"default": false,
Expand Down
2 changes: 1 addition & 1 deletion meshctrl.js
Original file line number Diff line number Diff line change
Expand Up @@ -1106,7 +1106,7 @@ function displayConfigHelp() {
}

function performConfigOperations(args) {
var domainValues = ['title', 'title2', 'titlepicture', 'trustedcert', 'welcomepicture', 'welcometext', 'userquota', 'meshquota', 'newaccounts', 'usernameisemail', 'newaccountemaildomains', 'newaccountspass', 'newaccountsrights', 'geolocation', 'lockagentdownload', 'userconsentflags', 'Usersessionidletimeout', 'auth', 'ldapoptions', 'ldapusername', 'ldapuserbinarykey', 'ldapuseremail', 'footer', 'certurl', 'loginKey', 'userallowedip', 'agentallowedip', 'agentnoproxy', 'agentconfig', 'orphanagentuser', 'httpheaders', 'yubikey', 'passwordrequirements', 'limits', 'amtacmactivation', 'redirects', 'sessionrecording', 'hide'];
var domainValues = ['title', 'title2', 'titlepicture', 'trustedcert', 'welcomepicture', 'welcometext', 'userquota', 'meshquota', 'newaccounts', 'usernameisemail', 'newaccountemaildomains', 'newaccountspass', 'newaccountsrights', 'geolocation', 'lockagentdownload', 'keyagents', 'keyagentsgrace', "keyagenttimeout", 'userconsentflags', 'Usersessionidletimeout', 'auth', 'ldapoptions', 'ldapusername', 'ldapuserbinarykey', 'ldapuseremail', 'footer', 'certurl', 'loginKey', 'userallowedip', 'agentallowedip', 'agentnoproxy', 'agentconfig', 'orphanagentuser', 'httpheaders', 'yubikey', 'passwordrequirements', 'limits', 'amtacmactivation', 'redirects', 'sessionrecording', 'hide'];
var domainObjectValues = ['ldapoptions', 'httpheaders', 'yubikey', 'passwordrequirements', 'limits', 'amtacmactivation', 'redirects', 'sessionrecording'];
var domainArrayValues = ['newaccountemaildomains', 'newaccountsrights', 'loginkey', 'agentconfig'];
var configChange = false;
Expand Down
9 changes: 8 additions & 1 deletion meshuser.js
Original file line number Diff line number Diff line change
Expand Up @@ -520,7 +520,7 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use
if (agentstats.invalidJsonCount > 0) { errorCountersCount++; errorCounters.InvalidJSON = agentstats.invalidJsonCount; }
if (agentstats.unknownAgentActionCount > 0) { errorCountersCount++; errorCounters.UnknownAction = agentstats.unknownAgentActionCount; }
if (agentstats.agentBadWebCertHashCount > 0) { errorCountersCount++; errorCounters.BadWebCertificate = agentstats.agentBadWebCertHashCount; }
if ((agentstats.agentBadSignature1Count + agentstats.agentBadSignature2Count) > 0) { errorCountersCount++; errorCounters.BadSignature = (agentstats.agentBadSignature1Count + agentstats.agentBadSignature2Count); }
if ((agentstats.agentBadSignature1Count + agentstats.agentBadSignature2Count + agentstats.agentBadSignature3Count) > 0) { errorCountersCount++; errorCounters.BadSignature = (agentstats.agentBadSignature1Count + agentstats.agentBadSignature2Count + agentstats.agentBadSignature3Count); }
if (agentstats.agentMaxSessionHoldCount > 0) { errorCountersCount++; errorCounters.MaxSessionsReached = agentstats.agentMaxSessionHoldCount; }
if ((agentstats.invalidDomainMeshCount + agentstats.invalidDomainMesh2Count) > 0) { errorCountersCount++; errorCounters.UnknownDeviceGroup = (agentstats.invalidDomainMeshCount + agentstats.invalidDomainMesh2Count); }
if ((agentstats.invalidMeshTypeCount + agentstats.invalidMeshType2Count) > 0) { errorCountersCount++; errorCounters.InvalidDeviceGroupType = (agentstats.invalidMeshTypeCount + agentstats.invalidMeshType2Count); }
Expand Down Expand Up @@ -2794,6 +2794,13 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use
if ((nodes != null) && (nodes.length == 1)) { db.Remove('da' + nodes[0].daid); } // Remove diagnostic agent to real agent link
db.Remove('ra' + node._id); // Remove real agent to diagnostic agent link
});
if (domain.keyagents || parent.parent.config.settings.keyagents) {
db.GetAllTypeNodeFiltered([nodeid], domain.id, 'agentkey', null, function (err, docs) {
for (let _doc of docs) {
db.Remove(_doc._id);
}
})
}

// Remove any user node links
if (node.links != null) {
Expand Down
Loading
Loading