@aikidosec/firewall
Version:
Zen by Aikido is an embedded Application Firewall that autonomously protects Node.js apps against common and critical attacks, provides rate limiting, detects malicious traffic (including bots), and more.
222 lines (221 loc) • 8.56 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.ServiceConfig = void 0;
const IPMatcher_1 = require("../helpers/ip-matcher/IPMatcher");
const matchEndpoints_1 = require("../helpers/matchEndpoints");
const isPrivateIP_1 = require("../vulnerabilities/ssrf/isPrivateIP");
const safeCreateRegExp_1 = require("./safeCreateRegExp");
class ServiceConfig {
constructor(endpoints, lastUpdatedAt, blockedUserIds, bypassedIPAddresses, receivedAnyStats, blockedIPAddresses, allowedIPAddresses) {
this.lastUpdatedAt = lastUpdatedAt;
this.receivedAnyStats = receivedAnyStats;
this.blockedUserIds = new Map();
this.nonGraphQLEndpoints = [];
this.graphqlFields = [];
this.blockedIPAddresses = [];
// If not empty, only ips in this list are allowed to access the service
// e.g. for country allowlists
this.allowedIPAddresses = [];
this.monitoredIPAddresses = [];
this.userAgentDetails = [];
this.blockNewOutgoingRequests = false;
this.domains = new Map();
this.setBlockedUserIds(blockedUserIds);
this.setBypassedIPAddresses(bypassedIPAddresses);
this.setEndpoints(endpoints);
this.setBlockedIPAddresses(blockedIPAddresses);
this.setAllowedIPAddresses(allowedIPAddresses);
}
setEndpoints(endpointConfigs) {
this.nonGraphQLEndpoints = [];
this.graphqlFields = [];
for (const endpoint of endpointConfigs) {
let allowedIPAddresses = undefined;
if (Array.isArray(endpoint.allowedIPAddresses) &&
endpoint.allowedIPAddresses.length > 0) {
allowedIPAddresses = new IPMatcher_1.IPMatcher(endpoint.allowedIPAddresses);
}
const endpointConfig = { ...endpoint, allowedIPAddresses };
if (endpoint.graphql) {
this.graphqlFields.push(endpointConfig);
}
else {
this.nonGraphQLEndpoints.push(endpointConfig);
}
}
}
getEndpoints(context) {
return (0, matchEndpoints_1.matchEndpoints)(context, this.nonGraphQLEndpoints);
}
getGraphQLField(context, name, operationType) {
const endpoints = (0, matchEndpoints_1.matchEndpoints)(context, this.graphqlFields.filter((field) => {
if (!field.graphql) {
return false;
}
return (field.graphql.name === name && field.graphql.type === operationType);
}));
return endpoints.length > 0 ? endpoints[0] : undefined;
}
setBypassedIPAddresses(ipAddresses) {
if (ipAddresses.length === 0) {
this.bypassedIPAddresses = undefined;
return;
}
this.bypassedIPAddresses = new IPMatcher_1.IPMatcher(ipAddresses);
}
isBypassedIP(ip) {
return this.bypassedIPAddresses ? this.bypassedIPAddresses.has(ip) : false;
}
setBlockedUserIds(blockedUserIds) {
this.blockedUserIds = new Map();
blockedUserIds.forEach((userId) => {
this.blockedUserIds.set(userId, userId);
});
}
isUserBlocked(userId) {
return this.blockedUserIds.has(userId);
}
isIPAddressBlocked(ip) {
const blocklist = this.blockedIPAddresses.find((blocklist) => blocklist.blocklist.has(ip));
if (blocklist) {
return { blocked: true, reason: blocklist.description };
}
return { blocked: false };
}
setBlockedIPAddresses(blockedIPAddresses) {
this.blockedIPAddresses = [];
for (const source of blockedIPAddresses) {
this.blockedIPAddresses.push({
key: source.key,
blocklist: new IPMatcher_1.IPMatcher(source.ips),
description: source.description,
});
}
}
updateBlockedIPAddresses(blockedIPAddresses) {
this.setBlockedIPAddresses(blockedIPAddresses);
}
updateMonitoredIPAddresses(monitoredIPAddresses) {
this.monitoredIPAddresses = [];
for (const source of monitoredIPAddresses) {
this.monitoredIPAddresses.push({
key: source.key,
list: new IPMatcher_1.IPMatcher(source.ips),
});
}
}
updateBlockedUserAgents(blockedUserAgents) {
if (!blockedUserAgents) {
// If an empty string is passed, we want to set the regex to undefined
// e.g. new RegExp("").test("abc") == true
this.blockedUserAgentRegex = undefined;
return;
}
this.blockedUserAgentRegex = (0, safeCreateRegExp_1.safeCreateRegExp)(blockedUserAgents, "i");
}
isUserAgentBlocked(ua) {
if (this.blockedUserAgentRegex) {
return { blocked: this.blockedUserAgentRegex.test(ua) };
}
return { blocked: false };
}
updateUserAgentDetails(userAgentDetails) {
this.userAgentDetails = [];
for (const detail of userAgentDetails) {
const regex = (0, safeCreateRegExp_1.safeCreateRegExp)(detail.pattern, "i");
if (regex) {
this.userAgentDetails.push({
key: detail.key,
pattern: regex,
});
}
}
}
updateMonitoredUserAgents(monitoredUserAgent) {
if (!monitoredUserAgent) {
// If an empty string is passed, we want to set the regex to undefined
// e.g. new RegExp("").test("abc") == true
this.monitoredUserAgentRegex = undefined;
return;
}
this.monitoredUserAgentRegex = (0, safeCreateRegExp_1.safeCreateRegExp)(monitoredUserAgent, "i");
}
isMonitoredUserAgent(ua) {
if (this.monitoredUserAgentRegex) {
return this.monitoredUserAgentRegex.test(ua);
}
return false;
}
getMatchingUserAgentKeys(ua) {
return this.userAgentDetails
.filter((details) => details.pattern.test(ua))
.map((details) => details.key);
}
getMatchingBlockedIPListKeys(ip) {
return this.blockedIPAddresses
.filter((list) => list.blocklist.has(ip))
.map((list) => list.key);
}
getMatchingMonitoredIPListKeys(ip) {
return this.monitoredIPAddresses
.filter((list) => list.list.has(ip))
.map((list) => list.key);
}
setAllowedIPAddresses(ipAddresses) {
this.allowedIPAddresses = [];
for (const source of ipAddresses) {
// Skip empty allowlists
if (source.ips.length === 0) {
continue;
}
this.allowedIPAddresses.push({
allowlist: new IPMatcher_1.IPMatcher(source.ips),
description: source.description,
});
}
}
updateAllowedIPAddresses(ipAddresses) {
this.setAllowedIPAddresses(ipAddresses);
}
isAllowedIPAddress(ip) {
if (this.allowedIPAddresses.length < 1) {
return { allowed: true };
}
// Always allow access from local IP addresses
if ((0, isPrivateIP_1.isPrivateIP)(ip)) {
return { allowed: true };
}
const allowlist = this.allowedIPAddresses.find((list) => list.allowlist.has(ip));
return { allowed: !!allowlist };
}
updateConfig(endpoints, lastUpdatedAt, blockedUserIds, bypassedIPAddresses, hasReceivedAnyStats) {
this.setEndpoints(endpoints);
this.setBlockedUserIds(blockedUserIds);
this.setBypassedIPAddresses(bypassedIPAddresses);
this.lastUpdatedAt = lastUpdatedAt;
this.receivedAnyStats = hasReceivedAnyStats;
}
getLastUpdatedAt() {
return this.lastUpdatedAt;
}
hasReceivedAnyStats() {
return this.receivedAnyStats;
}
setBlockNewOutgoingRequests(block) {
this.blockNewOutgoingRequests = block;
}
updateDomains(domains) {
this.domains = new Map(domains.map((i) => [i.hostname, i.mode]));
}
shouldBlockOutgoingRequest(hostname) {
const mode = this.domains.get(hostname);
if (this.blockNewOutgoingRequests) {
// Only allow outgoing requests if the mode is "allow"
// mode is undefined for unknown hostnames, so they get blocked
return mode !== "allow";
}
// Only block outgoing requests if the mode is "block"
return mode === "block";
}
}
exports.ServiceConfig = ServiceConfig;