zamza
Version:
Apache Kafka discovery, indexing, searches, storage, hooks and HTTP gateway
217 lines (216 loc) • 9.2 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const Debug = require("debug");
const debug = Debug("zamza:access");
const WILDCARD = "*";
const PERMISSIONS = {
DELETE: "__delete",
PRODUCE: "__produce",
HOOK: "__hook",
TOPIC: "__topic",
REPLAY: "__replay",
};
const ZAMZA_TOPIC_PREFIX = "__zamza";
class AccessControll {
constructor(accessConfig, metrics) {
this.accessConfig = accessConfig;
this.metrics = metrics;
if (!this.accessConfig || Object.keys(this.accessConfig).length <= 0) {
debug("NOTE: Automatically switched access configuration to allow everything.");
this.accessConfig = WILDCARD;
}
if (this.accessConfig === WILDCARD) {
debug("NOTE: You have NOT configured secured access to config and fetch endpoints.");
debug("Configured Access Controll: ", this.accessConfig);
}
else {
debug("Validating access configuration..");
if (!this.accessConfig || typeof this.accessConfig !== "object") {
throw new Error("Bad access configuration provided (object): " + JSON.stringify(this.accessConfig));
}
else {
Object.keys(this.accessConfig).map((key) => {
if (!key || key === WILDCARD) {
throw new Error("Multi token configuration does not allow null or wildcard as key: " + key);
}
if (!key.length || key.length < 5) {
throw new Error("Tokens should at least contain 6 characters.. " + key);
}
return this.accessConfig[key];
}).forEach((acv) => {
if (!Array.isArray(acv) && acv !== WILDCARD) {
throw new Error("Bad access configuration provided (key or wildcard): " + JSON.stringify(acv));
}
else {
if (Array.isArray(acv)) {
acv.forEach((acvv) => {
if (typeof acvv !== "string") {
throw new Error("Bad access configuration provided (array value string): "
+ JSON.stringify(acvv));
}
});
}
}
});
}
debug("Access configuration is valid.");
const anonymisedAccessConfig = {};
let i = 0;
Object.keys(this.accessConfig).forEach((token) => {
anonymisedAccessConfig[`${i}_${this.anonymiseToken(token)}`] = this.accessConfig[token];
i++;
});
debug("Configured Access Controll: ", anonymisedAccessConfig);
}
}
topicAccessAllowedForToken(token, topic) {
if (topic && topic.startsWith(ZAMZA_TOPIC_PREFIX)) {
debug("Cannot allow access to topic that begins with zamza's internal prefix", topic);
return false;
}
if (this.accessConfig === WILDCARD) {
this.metrics.inc("access_good");
return true;
}
if (!token) {
debug("Topic access not allowed for token", "token", "and topic", topic, "reason: no token.");
this.metrics.inc("access_bad");
return false;
}
const configuration = this.accessConfig[token];
if (!configuration) {
debug("Topic access not allowed for token", "token", "and topic", topic, "reason: no configuration.");
this.metrics.inc("access_bad");
return false;
}
if (configuration === WILDCARD) {
this.metrics.inc("access_good");
return true;
}
if (Array.isArray(configuration) && configuration.indexOf(topic) !== -1) {
this.metrics.inc("access_good");
return true;
}
if (Array.isArray(configuration) && configuration.indexOf(WILDCARD) !== -1) {
this.metrics.inc("access_good");
return true;
}
debug("Topic access not allowed for token", "token", "and topic", topic, "reason: topic not allowed.");
this.metrics.inc("access_bad");
return false;
}
wildcardAccessAllowedForToken(token) {
if (this.accessConfig === WILDCARD) {
this.metrics.inc("access_good");
return true;
}
if (!token) {
debug("Topic access not allowed for token", "token", "reason: no token.");
this.metrics.inc("access_bad");
return false;
}
const configuration = this.accessConfig[token];
if (!configuration) {
debug("Topic access not allowed for token", "token", "reason: no configuration.");
this.metrics.inc("access_bad");
return false;
}
if (configuration === WILDCARD) {
this.metrics.inc("access_good");
return true;
}
if (Array.isArray(configuration) && configuration.indexOf(WILDCARD) !== -1) {
this.metrics.inc("access_good");
return true;
}
debug("Topic access not allowed for token", "token", "reason: no wildcard.");
this.metrics.inc("access_bad");
return false;
}
permissionTypeAccessAllowedForToken(token, permissionType) {
if (this.accessConfig === WILDCARD) {
this.metrics.inc("access_good");
return true;
}
if (!token) {
debug(permissionType, "access not allowed for token", "token", "reason: no token.");
this.metrics.inc("access_bad");
return false;
}
const configuration = this.accessConfig[token];
if (!configuration) {
debug(permissionType, "access not allowed for token", "token", "reason: no configuration.");
this.metrics.inc("access_bad");
return false;
}
if (configuration === WILDCARD || configuration === permissionType) {
this.metrics.inc("access_good");
return true;
}
if (Array.isArray(configuration) && configuration.indexOf(WILDCARD) !== -1) {
this.metrics.inc("access_good");
return true;
}
if (Array.isArray(configuration) && configuration.indexOf(permissionType) !== -1) {
this.metrics.inc("access_good");
return true;
}
debug(permissionType, "access not allowed for token", "token", "reason: no wildcard, no permission.");
this.metrics.inc("access_bad");
return false;
}
anonymiseToken(token) {
if (!token) {
return "null|undefined";
}
let anonymisedToken = token.substr(0, 3);
for (let i = 3; i < token.length; i++) {
anonymisedToken += "#";
}
return anonymisedToken;
}
topicAccessAllowedForRequest(req, topic) {
const providedToken = req.headers ? req.headers.authorization : null;
return this.topicAccessAllowedForToken(providedToken, topic);
}
wildcardAccessAllowedForRequest(req) {
const providedToken = req.headers ? req.headers.authorization : null;
return this.wildcardAccessAllowedForToken(providedToken);
}
topicConfigAccessAllowedForRequest(req) {
const providedToken = req.headers ? req.headers.authorization : null;
return this.permissionTypeAccessAllowedForToken(providedToken, PERMISSIONS.TOPIC);
}
produceAccessAllowedForRequest(req) {
const providedToken = req.headers ? req.headers.authorization : null;
return this.permissionTypeAccessAllowedForToken(providedToken, PERMISSIONS.PRODUCE);
}
deleteAccessAllowedForRequest(req) {
const providedToken = req.headers ? req.headers.authorization : null;
return this.permissionTypeAccessAllowedForToken(providedToken, PERMISSIONS.DELETE);
}
hookAccessAllowedForRequest(req) {
const providedToken = req.headers ? req.headers.authorization : null;
return this.permissionTypeAccessAllowedForToken(providedToken, PERMISSIONS.HOOK);
}
subscriptionsAllowedForRequest(req, subscriptions) {
const providedToken = req.headers ? req.headers.authorization : null;
if (!Array.isArray(subscriptions)) {
throw new Error("Subscriptions must be an array.");
}
for (const subscription of subscriptions) {
if (!subscription || !subscription.topic) {
throw new Error("Subscription missing topic field.");
}
if (!this.topicAccessAllowedForToken(providedToken, subscription.topic)) {
return false;
}
}
return true;
}
replayAccessAllowedForRequest(req) {
const providedToken = req.headers ? req.headers.authorization : null;
return this.permissionTypeAccessAllowedForToken(providedToken, PERMISSIONS.REPLAY);
}
}
exports.default = AccessControll;