1
0
Fork 0
mirror of https://github.com/Ylianst/MeshCentral.git synced 2025-02-12 11:01:52 +00:00

Duo changes, but not yet fully tested.

This commit is contained in:
Ylian Saint-Hilaire 2024-12-22 19:10:35 -08:00
parent 1b01b90cd6
commit c92b88a374
7 changed files with 73 additions and 40 deletions

View file

@ -1665,24 +1665,9 @@
"description": "Set to false to disable SMS 2FA." "description": "Set to false to disable SMS 2FA."
}, },
"duo2factor": { "duo2factor": {
"type": "object", "type": "boolean",
"properties": { "default": true,
"integrationkey": { "description": "Set to false to disable Duo 2FA."
"type": "string",
"default": "",
"description": "Integration key from Duo"
},
"secretkey": {
"type": "string",
"default": "",
"description": "Secret key from Duo"
},
"apihostname": {
"type": "string",
"default": "",
"description": "API Hostname from Duo"
}
}
}, },
"push2factor": { "push2factor": {
"type": "boolean", "type": "boolean",
@ -2704,6 +2689,26 @@
}, },
"description": "This is used to create HTTP redirections. For example setting \"redirects\": { \"example\":\"https://example.com\" } will make it so that anyone accessing /example on the server will get redirected to the specified URL." "description": "This is used to create HTTP redirections. For example setting \"redirects\": { \"example\":\"https://example.com\" } will make it so that anyone accessing /example on the server will get redirected to the specified URL."
}, },
"duo2factor": {
"type": "object",
"properties": {
"integrationkey": {
"type": "string",
"default": "",
"description": "Integration key from Duo"
},
"secretkey": {
"type": "string",
"default": "",
"description": "Secret key from Duo"
},
"apihostname": {
"type": "string",
"default": "",
"description": "API Hostname from Duo"
}
}
},
"yubikey": { "yubikey": {
"type": "object", "type": "object",
"properties": { "properties": {

View file

@ -4230,7 +4230,7 @@ function mainStart() {
if (config.domains[i].sessionrecording != null) { sessionRecording = true; } if (config.domains[i].sessionrecording != null) { sessionRecording = true; }
if ((config.domains[i].passwordrequirements != null) && (config.domains[i].passwordrequirements.bancommonpasswords == true)) { wildleek = true; } if ((config.domains[i].passwordrequirements != null) && (config.domains[i].passwordrequirements.bancommonpasswords == true)) { wildleek = true; }
if ((config.domains[i].newaccountscaptcha != null) && (config.domains[i].newaccountscaptcha !== false)) { captcha = true; } if ((config.domains[i].newaccountscaptcha != null) && (config.domains[i].newaccountscaptcha !== false)) { captcha = true; }
if ((config.domains[i].passwordrequirements != null) && (typeof config.domains[i].passwordrequirements.duo2factor == 'object') && (passport.indexOf('@duosecurity/duo_universal') == -1)) { passport.push('@duosecurity/duo_universal'); } if ((typeof config.domains[i].duo2factor == 'object') && (passport.indexOf('@duosecurity/duo_universal') == -1)) { passport.push('@duosecurity/duo_universal'); }
} }
// Build the list of required modules // Build the list of required modules

View file

@ -3633,6 +3633,12 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use
// Do not allow this command if 2FA's are locked // Do not allow this command if 2FA's are locked
if ((domain.passwordrequirements) && (domain.passwordrequirements.lock2factor == true)) return; if ((domain.passwordrequirements) && (domain.passwordrequirements.lock2factor == true)) return;
// Do not allow if Duo is not supported
if ((typeof domain.duo2factor != 'object') || (typeof domain.duo2factor.integrationkey != 'string') || (typeof domain.duo2factor.secretkey != 'string') || (typeof domain.duo2factor.apihostname != 'string')) return;
// Do not allow if Duo is disabled
if ((typeof domain.passwordrequirements == 'object') && (domain.passwordrequirements.duo2factor == false)) return;
// Do not allow this command when logged in using a login token // Do not allow this command when logged in using a login token
if (req.session.loginToken != null) break; if (req.session.loginToken != null) break;

View file

@ -37,19 +37,33 @@
"sample-config-advanced.json" "sample-config-advanced.json"
], ],
"dependencies": { "dependencies": {
"@duosecurity/duo_universal": "2.0.3",
"@seald-io/nedb": "4.0.4", "@seald-io/nedb": "4.0.4",
"archiver": "7.0.1", "archiver": "7.0.1",
"body-parser": "1.20.3", "body-parser": "1.20.3",
"cbor": "5.2.0", "cbor": "5.2.0",
"compression": "1.7.5", "compression": "1.7.5",
"connect-flash": "0.1.1",
"cookie-session": "2.1.0", "cookie-session": "2.1.0",
"express": "4.21.2", "express": "4.21.2",
"express-handlebars": "7.1.3", "express-handlebars": "7.1.3",
"express-ws": "5.0.2", "express-ws": "5.0.2",
"firebase-admin": "12.7.0",
"ipcheck": "0.1.0", "ipcheck": "0.1.0",
"jwt-simple": "0.5.6",
"loadavg-windows": "1.1.1",
"minimist": "1.2.8", "minimist": "1.2.8",
"multiparty": "4.2.3", "multiparty": "4.2.3",
"node-forge": "1.3.1", "node-forge": "1.3.1",
"node-windows": "0.1.14",
"otplib": "10.2.3",
"passport": "0.7.0",
"passport-azure-oauth2": "0.1.0",
"passport-github2": "0.1.12",
"passport-google-oauth20": "2.0.0",
"passport-saml": "3.2.4",
"passport-twitter": "1.0.4",
"ssh2": "1.16.0",
"ua-parser-js": "1.0.39", "ua-parser-js": "1.0.39",
"ws": "8.18.0", "ws": "8.18.0",
"yauzl": "2.10.0" "yauzl": "2.10.0"

View file

@ -454,6 +454,11 @@
"_redirects": { "_redirects": {
"meshcommander": "https://www.meshcommander.com/" "meshcommander": "https://www.meshcommander.com/"
}, },
"_duo2factor": {
"integrationkey": "mykey",
"secretkey": "mysecret",
"apihostname": "api-xxxxxxxxxxx.duosecurity.com"
},
"_yubikey": { "_yubikey": {
"id": "0000", "id": "0000",
"secret": "xxxxxxxxxxxxxxxxxxxxx", "secret": "xxxxxxxxxxxxxxxxxxxxx",

View file

@ -2358,9 +2358,11 @@
QV('authPhoneNumberCheck', (userinfo.phone != null)); QV('authPhoneNumberCheck', (userinfo.phone != null));
QV('authMessagingCheck', (userinfo.msghandle != null)); QV('authMessagingCheck', (userinfo.msghandle != null));
QV('authEmailSetupCheck', (userinfo.otpekey == 1) && (userinfo.email != null) && (userinfo.emailVerified == true)); QV('authEmailSetupCheck', (userinfo.otpekey == 1) && (userinfo.email != null) && (userinfo.emailVerified == true));
QV('authDuoSetupCheck', (userinfo.otpduo == 1)); QV('authDuoSetupCheck', (userinfo.otpduo == 1) && ((features2 & 0x20000000) != 0));
QV('authAppSetupCheck', userinfo.otpsecret == 1); QV('authAppSetupCheck', userinfo.otpsecret == 1);
QV('manageAuthApp', (serverinfo.lock2factor != true) && ((userinfo.otpsecret == 1) || ((features2 & 0x00020000) == 0))); QV('manageAuthApp', (serverinfo.lock2factor != true) && ((userinfo.otpsecret == 1) || ((features2 & 0x00020000) == 0)));
QV('manageDuoApp', (serverinfo.lock2factor != true) && ((features2 & 0x20000000) != 0));
console.log('duo', (serverinfo.lock2factor != true) && ((features2 & 0x20000000) != 0));
QV('authKeySetupCheck', userinfo.otphkeys > 0); QV('authKeySetupCheck', userinfo.otphkeys > 0);
QV('authPushAuthDevCheck', (userinfo.otpdev > 0) && ((features2 & 0x40) != 0)); QV('authPushAuthDevCheck', (userinfo.otpdev > 0) && ((features2 & 0x40) != 0));
QV('authCodesSetupCheck', userinfo.otpkeys > 0); QV('authCodesSetupCheck', userinfo.otpkeys > 0);
@ -12884,11 +12886,11 @@
} }
function account_manageAuthDuo() { function account_manageAuthDuo() {
if (xxdialogMode || ((features & 0x00800000) == 0)) return; if (xxdialogMode || ((features2 & 0x20000000) == 0)) return;
var duoU2Fenabled = ((userinfo.otpduo == 1)); var duoU2Fenabled = ((userinfo.otpduo == 1));
setDialogMode(2, "Duo Authentication", 1, function () { setDialogMode(2, "Duo Authentication", 1, function () {
if (duoU2Fenabled != Q('duo2facheck').checked) { meshserver.send({ action: 'otpduo', enabled: Q('duo2facheck').checked }); } if (duoU2Fenabled != Q('duo2facheck').checked) { meshserver.send({ action: 'otpduo', enabled: Q('duo2facheck').checked }); }
}, "When enabled, on each login, you will be given the option to login using Duo for added security." + '<br /><br /><label><input id=duo2facheck type=checkbox ' + (duoU2Fenabled?'checked':'') + '/>' + "Enable Duo two-factor authentication." + '</label>'); }, "When enabled, on each login, you will be given the option to use Duo for added security." + '<br /><br /><label><input id=duo2facheck type=checkbox ' + (duoU2Fenabled?'checked':'') + '/>' + "Enable Duo two-factor authentication." + '</label>');
} }
function account_manageAuthApp() { function account_manageAuthApp() {

View file

@ -1162,7 +1162,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates, doneF
var sms2fa = (((typeof domain.passwordrequirements != 'object') || (domain.passwordrequirements.sms2factor != false)) && (parent.smsserver != null) && (user.phone != null)); var sms2fa = (((typeof domain.passwordrequirements != 'object') || (domain.passwordrequirements.sms2factor != false)) && (parent.smsserver != null) && (user.phone != null));
var msg2fa = (((typeof domain.passwordrequirements != 'object') || (domain.passwordrequirements.msg2factor != false)) && (parent.msgserver != null) && (parent.msgserver.providers != 0) && (user.msghandle != null)); var msg2fa = (((typeof domain.passwordrequirements != 'object') || (domain.passwordrequirements.msg2factor != false)) && (parent.msgserver != null) && (parent.msgserver.providers != 0) && (user.msghandle != null));
var push2fa = ((parent.firebase != null) && (user.otpdev != null)); var push2fa = ((parent.firebase != null) && (user.otpdev != null));
var duo2fa = (((typeof domain.passwordrequirements != 'object') || (typeof domain.passwordrequirements.duo2factor == 'object')) && (user.otpduo != null)); var duo2fa = ((((typeof domain.duo2factor == 'object') && (typeof domain.duo2factor.integrationkey == 'string') && (typeof domain.duo2factor.secretkey == 'string') && (typeof domain.duo2factor.apihostname == 'string')) || ((typeof domain.passwordrequirements != 'object') && (typeof domain.passwordrequirements.duo2factor != false))) && (user.otpduo != null));
// Check if two factor can be skipped // Check if two factor can be skipped
const twoFactorSkip = checkUserOneTimePasswordSkip(domain, user, req, loginOptions); const twoFactorSkip = checkUserOneTimePasswordSkip(domain, user, req, loginOptions);
@ -1212,13 +1212,13 @@ module.exports.CreateWebServer = function (parent, db, args, certificates, doneF
return; return;
} }
if ((req.body.hwtoken == '**duo**') && duo2fa) { if ((req.body.hwtoken == '**duo**') && duo2fa && (typeof domain.duo2factor == 'object') && (typeof domain.duo2factor.integrationkey == 'string') && (typeof domain.duo2factor.secretkey == 'string') && (typeof domain.duo2factor.apihostname == 'string')) {
// Redirect to duo here // Redirect to duo here
const duo = require('@duosecurity/duo_universal'); const duo = require('@duosecurity/duo_universal');
const client = new duo.Client({ const client = new duo.Client({
clientId: domain.passwordrequirements.duo2factor.integrationkey, clientId: domain.duo2factor.integrationkey,
clientSecret: domain.passwordrequirements.duo2factor.secretkey, clientSecret: domain.duo2factor.secretkey,
apiHost: domain.passwordrequirements.duo2factor.apihostname, apiHost: domain.duo2factor.apihostname,
redirectUrl: obj.generateBaseURL(domain, req) + 'auth-duo' + (domain.loginkey != null ? ('?key=' + domain.loginkey) : '') redirectUrl: obj.generateBaseURL(domain, req) + 'auth-duo' + (domain.loginkey != null ? ('?key=' + domain.loginkey) : '')
}); });
// Decrypt any session data // Decrypt any session data
@ -3302,6 +3302,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates, doneF
if ((parent.msgserver != null) && (parent.msgserver.providers != 0) && ((typeof domain.passwordrequirements != 'object') || (domain.passwordrequirements.msg2factor != false))) { features2 += 0x04000000; } // User messaging 2FA is allowed if ((parent.msgserver != null) && (parent.msgserver.providers != 0) && ((typeof domain.passwordrequirements != 'object') || (domain.passwordrequirements.msg2factor != false))) { features2 += 0x04000000; } // User messaging 2FA is allowed
if (domain.scrolltotop == true) { features2 += 0x08000000; } // Show the "Scroll to top" button if (domain.scrolltotop == true) { features2 += 0x08000000; } // Show the "Scroll to top" button
if (domain.devicesearchbargroupname === true) { features2 += 0x10000000; } // Search bar will find by group name too if (domain.devicesearchbargroupname === true) { features2 += 0x10000000; } // Search bar will find by group name too
if (((typeof domain.passwordrequirements != 'object') || (domain.passwordrequirements.duo2factor != false)) && (typeof domain.duo2factor == 'object') && (typeof domain.duo2factor.integrationkey == 'string') && (typeof domain.duo2factor.secretkey == 'string') && (typeof domain.duo2factor.apihostname == 'string')) { features2 += 0x20000000; } // using Duo for 2FA is allowed
return { features: features, features2: features2 }; return { features: features, features2: features2 };
} }
@ -3341,7 +3342,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates, doneF
var otpemail = (loginmode != 5) && (domain.mailserver != null) && (req.session != null) && ((req.session.temail === 1) || (typeof req.session.temail == 'string')); var otpemail = (loginmode != 5) && (domain.mailserver != null) && (req.session != null) && ((req.session.temail === 1) || (typeof req.session.temail == 'string'));
if ((typeof domain.passwordrequirements == 'object') && (domain.passwordrequirements.email2factor == false)) { otpemail = false; } if ((typeof domain.passwordrequirements == 'object') && (domain.passwordrequirements.email2factor == false)) { otpemail = false; }
var otpduo = (req.session != null) && (req.session.tduo === 1); var otpduo = (req.session != null) && (req.session.tduo === 1);
if ((typeof domain.passwordrequirements == 'object') && (domain.passwordrequirements.duo2factor == false)) { otpduo = false; } if (((typeof domain.passwordrequirements == 'object') && (domain.passwordrequirements.duo2factor == false)) || (typeof domain.duo2factor != 'object')) { otpduo = false; }
var otpsms = (parent.smsserver != null) && (req.session != null) && (req.session.tsms === 1); var otpsms = (parent.smsserver != null) && (req.session != null) && (req.session.tsms === 1);
if ((typeof domain.passwordrequirements == 'object') && (domain.passwordrequirements.sms2factor == false)) { otpsms = false; } if ((typeof domain.passwordrequirements == 'object') && (domain.passwordrequirements.sms2factor == false)) { otpsms = false; }
var otpmsg = (parent.msgserver != null) && (req.session != null) && (req.session.tmsg === 1); var otpmsg = (parent.msgserver != null) && (req.session != null) && (req.session.tmsg === 1);
@ -6950,14 +6951,14 @@ module.exports.CreateWebServer = function (parent, db, args, certificates, doneF
} }
} }
// // Setup Duo callback if needed // Setup Duo callback if needed
if ((typeof domain.passwordrequirements == 'object') && (typeof domain.passwordrequirements.duo2factor == 'object')) { if ((typeof domain.duo2factor == 'object') && (typeof domain.duo2factor.integrationkey == 'string') && (typeof domain.duo2factor.secretkey == 'string') && (typeof domain.duo2factor.apihostname == 'string')) {
obj.app.get(url + 'auth-duo', function (req, res){ obj.app.get(url + 'auth-duo', function (req, res){
var domain = getDomain(req); var domain = getDomain(req);
const sec = parent.decryptSessionData(req.session.e); const sec = parent.decryptSessionData(req.session.e);
if (req.query.state !== sec.duostate) { if (req.query.state !== sec.duostate) {
// the state returned from Duo IS NOT the same as what was in the session, so must fail! // The state returned from Duo IS NOT the same as what was in the session, so must fail
parent.debug('web', 'handleRootRequest: duo 2fa state failed!'); parent.debug('web', 'handleRootRequest: Duo 2FA state failed.');
req.session.loginmode = 1; req.session.loginmode = 1;
req.session.messageid = 117; // Invalid security check req.session.messageid = 117; // Invalid security check
res.redirect(domain.url + getQueryPortion(req)); // redirect back to main page res.redirect(domain.url + getQueryPortion(req)); // redirect back to main page
@ -6966,26 +6967,26 @@ module.exports.CreateWebServer = function (parent, db, args, certificates, doneF
// User credentials are stored in session, just check again and get userid // User credentials are stored in session, just check again and get userid
obj.authenticate(sec.tuser, sec.tpass, domain, function (err, userid, passhint, loginOptions) { obj.authenticate(sec.tuser, sec.tpass, domain, function (err, userid, passhint, loginOptions) {
if ((userid != null) && (err == null)) { if ((userid != null) && (err == null)) {
// Login data correct, now exchange authorization code for 2fa // Login data correct, now exchange authorization code for 2FA
const duo = require('@duosecurity/duo_universal'); const duo = require('@duosecurity/duo_universal');
const client = new duo.Client({ const client = new duo.Client({
clientId: domain.passwordrequirements.duo2factor.integrationkey, clientId: domain.duo2factor.integrationkey,
clientSecret: domain.passwordrequirements.duo2factor.secretkey, clientSecret: domain.duo2factor.secretkey,
apiHost: domain.passwordrequirements.duo2factor.apihostname, apiHost: domain.duo2factor.apihostname,
redirectUrl: obj.generateBaseURL(domain, req) + 'auth-duo' + (domain.loginkey != null ? ('?key=' + domain.loginkey) : '') redirectUrl: obj.generateBaseURL(domain, req) + 'auth-duo' + (domain.loginkey != null ? ('?key=' + domain.loginkey) : '')
}); });
client.exchangeAuthorizationCodeFor2FAResult(req.query.duo_code, userid).then(function (data) { client.exchangeAuthorizationCodeFor2FAResult(req.query.duo_code, userid).then(function (data) {
parent.debug('web', 'handleRootRequest: duo 2fa auth ok.'); parent.debug('web', 'handleRootRequest: Duo 2FA auth ok.');
req.session.userid = userid; req.session.userid = userid;
delete req.session.currentNode; delete req.session.currentNode;
req.session.ip = req.clientIp; // Bind this session to the IP address of the request req.session.ip = req.clientIp; // Bind this session to the IP address of the request
setSessionRandom(req); setSessionRandom(req);
obj.parent.authLog('https', 'Accepted duo authentication for ' + userid + ' from ' + req.clientIp + ' port ' + req.connection.remotePort, { useragent: req.headers['user-agent'], sessionid: req.session.x }); obj.parent.authLog('https', 'Accepted Duo authentication for ' + userid + ' from ' + req.clientIp + ' port ' + req.connection.remotePort, { useragent: req.headers['user-agent'], sessionid: req.session.x });
res.redirect(domain.url + getQueryPortion(req)); res.redirect(domain.url + getQueryPortion(req));
}).catch(function (err) { }).catch(function (err) {
// Duo 2FA exchange failed, so must fail! // Duo 2FA exchange failed
console.log('err',err); console.log('err',err);
parent.debug('web', 'handleRootRequest: duo 2fa exchange authorization code failed!.'); parent.debug('web', 'handleRootRequest: Duo 2FA exchange authorization code failed.');
req.session.loginmode = 1; req.session.loginmode = 1;
req.session.messageid = 117; // Invalid security check req.session.messageid = 117; // Invalid security check
res.redirect(domain.url + getQueryPortion(req)); res.redirect(domain.url + getQueryPortion(req));