added option for ntlm authorization

This commit is contained in:
Christopher Pickering 2022-05-13 12:58:23 -05:00
parent 23ce7c6623
commit ef73af391f
No known key found for this signature in database
GPG key ID: E14DB3B0A0FACF84
8 changed files with 133 additions and 22 deletions

View file

@ -3,13 +3,16 @@ BEGIN TRANSACTION;
ALTER TABLE monitor ALTER TABLE monitor
ADD auth_method VARCHAR(250); ADD auth_method VARCHAR(250);
COMMIT ALTER TABLE monitor
ADD auth_domain TEXT;
ALTER TABLE monitor
ADD auth_workstation TEXT;
BEGIN TRANSACTION;
UPDATE monitor
SET auth_method = 'basic'
WHERE basic_auth_user is not null
COMMIT; COMMIT;
BEGIN TRANSACTION;
UPDATE monitor
SET auth_method = 'basic'
WHERE basic_auth_user is not null;
COMMIT;

47
package-lock.json generated
View file

@ -17,6 +17,7 @@
"@popperjs/core": "~2.10.2", "@popperjs/core": "~2.10.2",
"args-parser": "~1.3.0", "args-parser": "~1.3.0",
"axios": "~0.26.1", "axios": "~0.26.1",
"axios-ntlm": "^1.3.0",
"badge-maker": "^3.3.1", "badge-maker": "^3.3.1",
"bcryptjs": "~2.4.3", "bcryptjs": "~2.4.3",
"bootstrap": "5.1.3", "bootstrap": "5.1.3",
@ -4129,6 +4130,23 @@
"follow-redirects": "^1.14.8" "follow-redirects": "^1.14.8"
} }
}, },
"node_modules/axios-ntlm": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/axios-ntlm/-/axios-ntlm-1.3.0.tgz",
"integrity": "sha512-NPNsIMO1SGX5scs3ZWJqsV7iRLvET+DlRl94aZ7Sx14zA8RTQh9EDxsJmxB9cKjardKfp2Vge444uYYLfvWC0Q==",
"dependencies": {
"axios": "^0.21.3",
"dev-null": "^0.1.1"
}
},
"node_modules/axios-ntlm/node_modules/axios": {
"version": "0.21.4",
"resolved": "https://registry.npmjs.org/axios/-/axios-0.21.4.tgz",
"integrity": "sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==",
"dependencies": {
"follow-redirects": "^1.14.0"
}
},
"node_modules/babel-jest": { "node_modules/babel-jest": {
"version": "27.5.1", "version": "27.5.1",
"resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-27.5.1.tgz", "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-27.5.1.tgz",
@ -5938,6 +5956,11 @@
"node": ">=8" "node": ">=8"
} }
}, },
"node_modules/dev-null": {
"version": "0.1.1",
"resolved": "https://registry.npmjs.org/dev-null/-/dev-null-0.1.1.tgz",
"integrity": "sha1-WiBc48Ky73e2I41roXnrdMag6Bg="
},
"node_modules/devtools-protocol": { "node_modules/devtools-protocol": {
"version": "0.0.948846", "version": "0.0.948846",
"resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.948846.tgz", "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.948846.tgz",
@ -19978,6 +20001,25 @@
"follow-redirects": "^1.14.8" "follow-redirects": "^1.14.8"
} }
}, },
"axios-ntlm": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/axios-ntlm/-/axios-ntlm-1.3.0.tgz",
"integrity": "sha512-NPNsIMO1SGX5scs3ZWJqsV7iRLvET+DlRl94aZ7Sx14zA8RTQh9EDxsJmxB9cKjardKfp2Vge444uYYLfvWC0Q==",
"requires": {
"axios": "^0.21.3",
"dev-null": "^0.1.1"
},
"dependencies": {
"axios": {
"version": "0.21.4",
"resolved": "https://registry.npmjs.org/axios/-/axios-0.21.4.tgz",
"integrity": "sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==",
"requires": {
"follow-redirects": "^1.14.0"
}
}
}
},
"babel-jest": { "babel-jest": {
"version": "27.5.1", "version": "27.5.1",
"resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-27.5.1.tgz", "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-27.5.1.tgz",
@ -21376,6 +21418,11 @@
"integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==", "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==",
"dev": true "dev": true
}, },
"dev-null": {
"version": "0.1.1",
"resolved": "https://registry.npmjs.org/dev-null/-/dev-null-0.1.1.tgz",
"integrity": "sha1-WiBc48Ky73e2I41roXnrdMag6Bg="
},
"devtools-protocol": { "devtools-protocol": {
"version": "0.0.948846", "version": "0.0.948846",
"resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.948846.tgz", "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.948846.tgz",

View file

@ -68,6 +68,7 @@
"@popperjs/core": "~2.10.2", "@popperjs/core": "~2.10.2",
"args-parser": "~1.3.0", "args-parser": "~1.3.0",
"axios": "~0.26.1", "axios": "~0.26.1",
"axios-ntlm": "^1.3.0",
"badge-maker": "^3.3.1", "badge-maker": "^3.3.1",
"bcryptjs": "~2.4.3", "bcryptjs": "~2.4.3",
"bootstrap": "5.1.3", "bootstrap": "5.1.3",

View file

@ -58,6 +58,7 @@ class Database {
"patch-monitor-expiry-notification.sql": true, "patch-monitor-expiry-notification.sql": true,
"patch-status-page-footer-css.sql": true, "patch-status-page-footer-css.sql": true,
"patch-added-mqtt-monitor.sql": true, "patch-added-mqtt-monitor.sql": true,
"patch-add-other-auth.sql": { parents: [ "patch-monitor-basic-auth.sql" ] },
}; };
/** /**

View file

@ -7,7 +7,7 @@ dayjs.extend(timezone);
const axios = require("axios"); const axios = require("axios");
const { Prometheus } = require("../prometheus"); const { Prometheus } = require("../prometheus");
const { log, UP, DOWN, PENDING, flipStatus, TimeLogger } = require("../../src/util"); const { log, UP, DOWN, PENDING, flipStatus, TimeLogger } = require("../../src/util");
const { tcping, ping, dnsResolve, checkCertificate, checkStatusCode, getTotalClientInRoom, setting, mqttAsync } = require("../util-server"); const { tcping, ping, dnsResolve, checkCertificate, checkStatusCode, getTotalClientInRoom, setting, mqttAsync, httpNtlm } = require("../util-server");
const { R } = require("redbean-node"); const { R } = require("redbean-node");
const { BeanModel } = require("redbean-node/dist/bean-model"); const { BeanModel } = require("redbean-node/dist/bean-model");
const { Notification } = require("../notification"); const { Notification } = require("../notification");
@ -87,7 +87,10 @@ class Monitor extends BeanModel {
mqttUsername: this.mqttUsername, mqttUsername: this.mqttUsername,
mqttPassword: this.mqttPassword, mqttPassword: this.mqttPassword,
mqttTopic: this.mqttTopic, mqttTopic: this.mqttTopic,
mqttSuccessMessage: this.mqttSuccessMessage mqttSuccessMessage: this.mqttSuccessMessage,
authMethod: this.authMethod,
authWorkstation: this.authWorkstation,
authDomain: this.authDomain,
}; };
if (includeSensitiveData) { if (includeSensitiveData) {
@ -213,7 +216,7 @@ class Monitor extends BeanModel {
// HTTP basic auth // HTTP basic auth
let basicAuthHeader = {}; let basicAuthHeader = {};
if (this.basic_auth_user) { if (this.auth_method === "basic") {
basicAuthHeader = { basicAuthHeader = {
"Authorization": "Basic " + this.encodeBase64(this.basic_auth_user, this.basic_auth_pass), "Authorization": "Basic " + this.encodeBase64(this.basic_auth_user, this.basic_auth_pass),
}; };
@ -264,7 +267,21 @@ class Monitor extends BeanModel {
log.debug("monitor", `[${this.name}] Axios Options: ${JSON.stringify(options)}`); log.debug("monitor", `[${this.name}] Axios Options: ${JSON.stringify(options)}`);
log.debug("monitor", `[${this.name}] Axios Request`); log.debug("monitor", `[${this.name}] Axios Request`);
let res = await axios.request(options); let res;
if (this.auth_method === "ntlm") {
options.httpsAgent.keepAlive = true;
res = await httpNtlm(options, {
username: this.basic_auth_user,
password: this.basic_auth_pass,
domain: this.authDomain,
workstation: this.authWorkstation,
});
} else {
res = await axios.request(options);
}
bean.msg = `${res.status} - ${res.statusText}`; bean.msg = `${res.status} - ${res.statusText}`;
bean.ping = dayjs().valueOf() - startTime; bean.ping = dayjs().valueOf() - startTime;

View file

@ -674,6 +674,9 @@ try {
bean.mqttPassword = monitor.mqttPassword; bean.mqttPassword = monitor.mqttPassword;
bean.mqttTopic = monitor.mqttTopic; bean.mqttTopic = monitor.mqttTopic;
bean.mqttSuccessMessage = monitor.mqttSuccessMessage; bean.mqttSuccessMessage = monitor.mqttSuccessMessage;
bean.authMethod = monitor.authMethod;
bean.authWorkstation = monitor.authWorkstation;
bean.authDomain = monitor.authDomain;
await R.store(bean); await R.store(bean);
@ -1247,8 +1250,11 @@ try {
method: monitorListData[i].method || "GET", method: monitorListData[i].method || "GET",
body: monitorListData[i].body, body: monitorListData[i].body,
headers: monitorListData[i].headers, headers: monitorListData[i].headers,
authMethod: monitorListData[i].authMethod,
basic_auth_user: monitorListData[i].basic_auth_user, basic_auth_user: monitorListData[i].basic_auth_user,
basic_auth_pass: monitorListData[i].basic_auth_pass, basic_auth_pass: monitorListData[i].basic_auth_pass,
authWorkstation: monitorListData[i].authWorkstation,
authDomain: monitorListData[i].authDomain,
interval: monitorListData[i].interval, interval: monitorListData[i].interval,
retryInterval: retryInterval, retryInterval: retryInterval,
hostname: monitorListData[i].hostname, hostname: monitorListData[i].hostname,

View file

@ -10,6 +10,7 @@ const chardet = require("chardet");
const mqtt = require("mqtt"); const mqtt = require("mqtt");
const chroma = require("chroma-js"); const chroma = require("chroma-js");
const { badgeConstants } = require("./config"); const { badgeConstants } = require("./config");
const { NtlmClient } = require("axios-ntlm");
// From ping-lite // From ping-lite
exports.WIN = /^win/.test(process.platform); exports.WIN = /^win/.test(process.platform);
@ -172,6 +173,26 @@ exports.mqttAsync = function (hostname, topic, okMessage, options = {}) {
}); });
}; };
/**
* Use NTLM Auth for a http request.
* @param {Object} options The http request options
* @param {Object} ntlmOptions The auth options
* @returns {Promise<(string[]|Object[]|Object)>}
*/
exports.httpNtlm = function (options, ntlmOptions) {
return new Promise((resolve, reject) => {
let client = NtlmClient(ntlmOptions);
client(options)
.then((resp) => {
resolve(resp);
})
.catch((err) => {
reject(err);
});
});
};
/** /**
* Resolves a given record using the specified DNS server * Resolves a given record using the specified DNS server
* @param {string} hostname The hostname of the record to lookup * @param {string} hostname The hostname of the record to lookup

View file

@ -358,15 +358,18 @@
<div class="my-3"> <div class="my-3">
<label for="method" class="form-label">{{ $t("Method") }}</label> <label for="method" class="form-label">{{ $t("Method") }}</label>
<select id="method" v-model="monitor.authMethod" class="form-select"> <select id="method" v-model="monitor.authMethod" class="form-select">
<option value="GET"> <option value="">
None
</option>
<option value="basic">
Basic Basic
</option> </option>
<option value="POST"> <option value="ntlm">
NTML NTLM
</option> </option>
</select> </select>
</div> </div>
<template v-if="monitor.authMethod && monitor.authMethod !== '' ">
<div class="my-3"> <div class="my-3">
<label for="basicauth" class="form-label">{{ $t("Username") }}</label> <label for="basicauth" class="form-label">{{ $t("Username") }}</label>
<input id="basicauth-user" v-model="monitor.basic_auth_user" type="text" class="form-control" :placeholder="$t('Username')"> <input id="basicauth-user" v-model="monitor.basic_auth_user" type="text" class="form-control" :placeholder="$t('Username')">
@ -376,6 +379,18 @@
<label for="basicauth" class="form-label">{{ $t("Password") }}</label> <label for="basicauth" class="form-label">{{ $t("Password") }}</label>
<input id="basicauth-pass" v-model="monitor.basic_auth_pass" type="password" autocomplete="new-password" class="form-control" :placeholder="$t('Password')"> <input id="basicauth-pass" v-model="monitor.basic_auth_pass" type="password" autocomplete="new-password" class="form-control" :placeholder="$t('Password')">
</div> </div>
<template v-if="monitor.authMethod === 'ntlm' ">
<div class="my-3">
<label for="basicauth" class="form-label">{{ $t("Domain") }}</label>
<input id="basicauth-domain" v-model="monitor.authDomain" type="text" class="form-control" :placeholder="$t('Domain')">
</div>
<div class="my-3">
<label for="basicauth" class="form-label">{{ $t("Workstation") }}</label>
<input id="basicauth-workstation" v-model="monitor.authWorkstation" type="password" autocomplete="new-password" class="form-control" :placeholder="$t('Workstation')">
</div>
</template>
</template>
</template> </template>
</div> </div>
</div> </div>