Merge pull request #1639 from christopherpickering/ntml-auth

Add NTML Auth Option for HTTP
This commit is contained in:
Louis Lam 2022-06-14 10:56:55 +08:00 committed by GitHub
commit 0e8f6d2f85
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 151 additions and 11 deletions

View file

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

47
package-lock.json generated
View file

@ -18,6 +18,7 @@
"args-parser": "~1.3.0",
"axios": "~0.26.1",
"axios-cached-dns-resolve": "^3.0.6",
"axios-ntlm": "^1.3.0",
"badge-maker": "^3.3.1",
"bcryptjs": "~2.4.3",
"bootstrap": "5.1.3",
@ -4647,6 +4648,23 @@
"node": ">=14.0.0"
}
},
"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": {
"version": "27.5.1",
"resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-27.5.1.tgz",
@ -6635,6 +6653,11 @@
"node": ">=8"
}
},
"node_modules/dev-null": {
"version": "0.1.1",
"resolved": "https://registry.npmjs.org/dev-null/-/dev-null-0.1.1.tgz",
"integrity": "sha512-nMNZG0zfMgmdv8S5O0TM5cpwNbGKRGPCxVsr0SmA3NZZy9CYBbuNLL0PD3Acx9e5LIUgwONXtM9kM6RlawPxEQ=="
},
"node_modules/devtools-protocol": {
"version": "0.0.948846",
"resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.948846.tgz",
@ -22317,6 +22340,25 @@
"pino-pretty": "^7.6.1"
}
},
"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": {
"version": "27.5.1",
"resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-27.5.1.tgz",
@ -23843,6 +23885,11 @@
"integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==",
"dev": true
},
"dev-null": {
"version": "0.1.1",
"resolved": "https://registry.npmjs.org/dev-null/-/dev-null-0.1.1.tgz",
"integrity": "sha512-nMNZG0zfMgmdv8S5O0TM5cpwNbGKRGPCxVsr0SmA3NZZy9CYBbuNLL0PD3Acx9e5LIUgwONXtM9kM6RlawPxEQ=="
},
"devtools-protocol": {
"version": "0.0.948846",
"resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.948846.tgz",

View file

@ -70,6 +70,7 @@
"args-parser": "~1.3.0",
"axios": "~0.26.1",
"axios-cached-dns-resolve": "^3.0.6",
"axios-ntlm": "^1.3.0",
"badge-maker": "^3.3.1",
"bcryptjs": "~2.4.3",
"bootstrap": "5.1.3",

View file

@ -59,6 +59,7 @@ class Database {
"patch-status-page-footer-css.sql": true,
"patch-added-mqtt-monitor.sql": true,
"patch-add-sqlserver-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 { Prometheus } = require("../prometheus");
const { log, UP, DOWN, PENDING, flipStatus, TimeLogger } = require("../../src/util");
const { tcping, ping, dnsResolve, checkCertificate, checkStatusCode, getTotalClientInRoom, setting, mssqlQuery, mqttAsync, setSetting } = require("../util-server");
const { tcping, ping, dnsResolve, checkCertificate, checkStatusCode, getTotalClientInRoom, setting, mssqlQuery, mqttAsync, setSetting, httpNtlm } = require("../util-server");
const { R } = require("redbean-node");
const { BeanModel } = require("redbean-node/dist/bean-model");
const { Notification } = require("../notification");
@ -96,6 +96,9 @@ class Monitor extends BeanModel {
mqttSuccessMessage: this.mqttSuccessMessage,
databaseConnectionString: this.databaseConnectionString,
databaseQuery: this.databaseQuery,
authMethod: this.authMethod,
authWorkstation: this.authWorkstation,
authDomain: this.authDomain,
};
if (includeSensitiveData) {
@ -221,7 +224,7 @@ class Monitor extends BeanModel {
// HTTP basic auth
let basicAuthHeader = {};
if (this.basic_auth_user) {
if (this.auth_method === "basic") {
basicAuthHeader = {
"Authorization": "Basic " + this.encodeBase64(this.basic_auth_user, this.basic_auth_pass),
};
@ -272,7 +275,21 @@ class Monitor extends BeanModel {
log.debug("monitor", `[${this.name}] Axios Options: ${JSON.stringify(options)}`);
log.debug("monitor", `[${this.name}] Axios Request`);
let res = await axiosClient.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 axiosClient.request(options);
}
bean.msg = `${res.status} - ${res.statusText}`;
bean.ping = dayjs().valueOf() - startTime;

View file

@ -671,6 +671,9 @@ let needSetup = false;
bean.mqttSuccessMessage = monitor.mqttSuccessMessage;
bean.databaseConnectionString = monitor.databaseConnectionString;
bean.databaseQuery = monitor.databaseQuery;
bean.authMethod = monitor.authMethod;
bean.authWorkstation = monitor.authWorkstation;
bean.authDomain = monitor.authDomain;
await R.store(bean);
@ -1244,8 +1247,11 @@ let needSetup = false;
method: monitorListData[i].method || "GET",
body: monitorListData[i].body,
headers: monitorListData[i].headers,
authMethod: monitorListData[i].authMethod,
basic_auth_user: monitorListData[i].basic_auth_user,
basic_auth_pass: monitorListData[i].basic_auth_pass,
authWorkstation: monitorListData[i].authWorkstation,
authDomain: monitorListData[i].authDomain,
interval: monitorListData[i].interval,
retryInterval: retryInterval,
hostname: monitorListData[i].hostname,

View file

@ -11,6 +11,7 @@ const mqtt = require("mqtt");
const chroma = require("chroma-js");
const { badgeConstants } = require("./config");
const mssql = require("mssql");
const { NtlmClient } = require("axios-ntlm");
// From ping-lite
exports.WIN = /^win/.test(process.platform);
@ -173,6 +174,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
* @param {string} hostname The hostname of the record to lookup

View file

@ -368,18 +368,46 @@
<textarea id="headers" v-model="monitor.headers" class="form-control" :placeholder="headersPlaceholder"></textarea>
</div>
<!-- HTTP Basic Auth -->
<h4 class="mt-5 mb-2">{{ $t("HTTP Basic Auth") }}</h4>
<!-- HTTP Auth -->
<h4 class="mt-5 mb-2">{{ $t("HTTP Authentication") }}</h4>
<!-- Method -->
<div class="my-3">
<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')">
<label for="method" class="form-label">{{ $t("Method") }}</label>
<select id="method" v-model="monitor.authMethod" class="form-select">
<option :value="null">
None
</option>
<option value="basic">
Basic
</option>
<option value="ntlm">
NTLM
</option>
</select>
</div>
<template v-if="monitor.authMethod && monitor.authMethod !== null ">
<div class="my-3">
<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')">
</div>
<div class="my-3">
<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')">
</div>
<div class="my-3">
<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')">
</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>
</div>
</div>
@ -569,6 +597,7 @@ export default {
mqttPassword: "",
mqttTopic: "",
mqttSuccessMessage: "",
authMethod: null,
};
if (this.$root.proxyList && !this.monitor.proxyId) {