@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.
231 lines (230 loc) • 9.45 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.ServiceConfig = void 0;
const addIPv4MappedAddresses_1 = require("../helpers/addIPv4MappedAddresses");
const IPMatcher_1 = require("../helpers/ip-matcher/IPMatcher");
const matchEndpoints_1 = require("../helpers/matchEndpoints");
const normalizeHostname_1 = require("../helpers/normalizeHostname");
const isPrivateIP_1 = require("../vulnerabilities/ssrf/isPrivateIP");
const safeCreateRegExp_1 = require("./safeCreateRegExp");
class ServiceConfig {
constructor(endpoints, lastUpdatedAt, blockedUserIds, bypassedIPAddresses, blockedIPAddresses, allowedIPAddresses) {
this.lastUpdatedAt = lastUpdatedAt;
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.excludedUserIdsFromRateLimiting = new Set();
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) {
// Small list, frequently accessed: add IPv4-mapped versions at creation time for fast lookups
allowedIPAddresses = new IPMatcher_1.IPMatcher((0, addIPv4MappedAddresses_1.addIPv4MappedAddresses)(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;
}
// Small list, frequently accessed: add IPv4-mapped versions at creation time for fast lookups
this.bypassedIPAddresses = new IPMatcher_1.IPMatcher((0, addIPv4MappedAddresses_1.addIPv4MappedAddresses)(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((list) => list.blocklist.hasWithMappedCheck(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,
// Large list: IPv4-mapped checked at lookup time to save memory
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,
// Large list: IPv4-mapped checked at lookup time to save memory
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.hasWithMappedCheck(ip))
.map((list) => list.key);
}
getMatchingMonitoredIPListKeys(ip) {
return this.monitoredIPAddresses
.filter((list) => list.list.hasWithMappedCheck(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({
// Large list: IPv4-mapped checked at lookup time to save memory
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.hasWithMappedCheck(ip));
return { allowed: !!allowlist };
}
updateConfig(endpoints, lastUpdatedAt, blockedUserIds, bypassedIPAddresses) {
this.setEndpoints(endpoints);
this.setBlockedUserIds(blockedUserIds);
this.setBypassedIPAddresses(bypassedIPAddresses);
this.lastUpdatedAt = lastUpdatedAt;
}
getLastUpdatedAt() {
return this.lastUpdatedAt;
}
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((0, normalizeHostname_1.normalizeHostname)(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";
}
updateUsersExcludedFromRateLimiting(userIds) {
this.excludedUserIdsFromRateLimiting = new Set(userIds);
}
isUserExcludedFromRateLimiting(userId) {
return this.excludedUserIdsFromRateLimiting.has(userId);
}
}
exports.ServiceConfig = ServiceConfig;