azure-kusto-data
Version:
Azure Data Explorer Query SDK
132 lines • 5.14 kB
JavaScript
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
import { getStringTailLowerCase } from "./utils.js";
import { default as endpointsData } from "./wellKnownKustoEndpoints.js";
export class MatchRule {
constructor(
/**
* The suffix which the candidate must end with in order to match.
*/
suffix,
/**
* Indicates whether the match must be exact (the candidate must
* not have any prefix) or not.
*/
exact) {
this.suffix = suffix;
this.exact = exact;
}
}
export class FastSuffixMatcher {
constructor(rules) {
this.rules = {};
this._suffixLength = rules ? Math.min(...rules.map((r) => r.suffix.length)) : 0;
const processedRules = {};
rules === null || rules === void 0 ? void 0 : rules.forEach((rule) => {
const suffix = getStringTailLowerCase(rule.suffix, this._suffixLength);
if (!processedRules[suffix]) {
processedRules[suffix] = [];
}
processedRules[suffix].push(rule);
});
this.rules = processedRules;
}
isMatch(candidate) {
if (candidate.length < this._suffixLength) {
return false;
}
const matchRules = this.rules[getStringTailLowerCase(candidate, this._suffixLength)];
if (matchRules) {
for (const rule of matchRules) {
if (candidate.endsWith(rule.suffix)) {
if (candidate.length === rule.suffix.length || !rule.exact) {
return true;
}
}
}
}
return false;
}
static createFromExisting(existing, rules) {
if (!existing || !Object.keys(existing.rules).length) {
return new FastSuffixMatcher(rules);
}
if (!rules) {
return existing;
}
return new FastSuffixMatcher(rules.concat(...Object.values(existing.rules)));
}
}
class KustoTrustedEndpointsImpl {
constructor() {
this.matchers = {};
this.additionalMatcher = null;
this.overrideMatcher = null; // We could unify this with matchers, but separating makes debugging easier
const etr = Object.entries(endpointsData.AllowedEndpointsByLogin);
for (const [k, v] of etr) {
const rules = [];
v.AllowedKustoSuffixes.forEach((suffix) => rules.push(new MatchRule(suffix, false)));
v.AllowedKustoHostnames.forEach((hostname) => rules.push(new MatchRule(hostname, true)));
this.matchers[k] = new FastSuffixMatcher(rules);
}
}
setOverridePolicy(matcher) {
this.overrideMatcher = matcher;
}
validateTrustedEndpoint(url, loginEndpoint) {
const uri = typeof url === "string" ? new URL(url) : url;
const host = uri.hostname;
// Check that target hostname is trusted and can accept security token
this._validateHostnameIsTrusted(host != null ? host : url.toString(), loginEndpoint);
}
addTrustedHosts(rules, replace) {
if (!(rules === null || rules === void 0 ? void 0 : rules.length)) {
if (replace) {
this.additionalMatcher = null;
}
return;
}
this.additionalMatcher = FastSuffixMatcher.createFromExisting(replace ? null : this.additionalMatcher, rules);
}
_validateHostnameIsTrusted(hostname, loginEndpoint) {
// The loopback is unconditionally allowed (since we trust ourselves)
if (this._isLocalAddress(hostname)) {
return;
}
// Either check the override matcher OR the matcher:
const override = this.overrideMatcher;
if (override != null) {
if (override(hostname)) {
return;
}
}
else {
const matcher = this.matchers[loginEndpoint === null || loginEndpoint === void 0 ? void 0 : loginEndpoint.toLowerCase()];
if (matcher === null || matcher === void 0 ? void 0 : matcher.isMatch(hostname)) {
return;
}
}
const additionalMatcher = this.additionalMatcher;
if (additionalMatcher === null || additionalMatcher === void 0 ? void 0 : additionalMatcher.isMatch(hostname)) {
return;
}
throw new Error(`Can't communicate with '${hostname}' as this hostname is currently not trusted; please see https://aka.ms/kustotrustedendpoints`);
}
_isLocalAddress(host) {
if (["localhost", "127.0.0.1", "::1", "[::1]"].find((c) => c === host)) {
return true;
}
if (host.startsWith("127.") && host.length <= 15 && host.length >= 9) {
for (let i = 0; i < host.length; i++) {
const ch = host.charAt(i);
if (ch !== "." && (ch < "0" || ch > "9")) {
return false;
}
}
return true;
}
return false;
}
}
export const kustoTrustedEndpoints = new KustoTrustedEndpointsImpl();
//# sourceMappingURL=kustoTrustedEndpoints.js.map