feat(security): compile network ranges and CIDR arrays into edge firewall policies

This commit is contained in:
2026-04-26 20:49:57 +00:00
parent 89ab918826
commit 7f3ab2499d
8 changed files with 241 additions and 19 deletions
+90 -8
View File
@@ -16,7 +16,7 @@ export interface ISecurityPolicyManagerOptions {
}
export interface IRemoteIngressFirewallSnapshot {
blockedIps?: string[];
blockedIps: string[];
}
export class SecurityPolicyManager {
@@ -122,6 +122,7 @@ export class SecurityPolicyManager {
registrantOrg: doc.registrantOrg,
registrantCountry: doc.registrantCountry,
networkRange: doc.networkRange,
networkCidrs: doc.networkCidrs,
abuseContact: doc.abuseContact,
country: doc.country,
countryCode: doc.countryCode,
@@ -205,16 +206,22 @@ export class SecurityPolicyManager {
}
if (rule.type === 'cidr') {
const cidr = this.normalizeCidr(normalizedValue);
if (cidr) blockedCidrs.add(cidr);
for (const cidr of this.normalizeNetworkEntries(normalizedValue)) {
blockedCidrs.add(cidr);
}
continue;
}
for (const doc of intelligenceDocs) {
if (!this.ruleMatchesIntelligence(rule, doc)) continue;
const cidr = this.normalizeCidr(doc.networkRange || '');
if (cidr) {
blockedCidrs.add(cidr);
const networkEntries = this.normalizeNetworkEntryList([
...(doc.networkCidrs || []),
doc.networkRange,
]);
if (networkEntries.length > 0) {
for (const cidr of networkEntries) {
blockedCidrs.add(cidr);
}
} else if (this.normalizeIp(doc.ipAddress)) {
blockedIps.add(this.normalizeIp(doc.ipAddress)!);
}
@@ -231,13 +238,13 @@ export class SecurityPolicyManager {
return await this.compilePolicy();
}
public async compileRemoteIngressFirewall(): Promise<IRemoteIngressFirewallSnapshot | undefined> {
public async compileRemoteIngressFirewall(): Promise<IRemoteIngressFirewallSnapshot> {
const policy = await this.compilePolicy();
const blockedIps = [
...policy.blockedIps.filter((ip) => plugins.net.isIP(ip) === 4),
...policy.blockedCidrs.filter((cidr) => plugins.net.isIP(cidr.split('/')[0]) === 4),
];
return blockedIps.length > 0 ? { blockedIps } : undefined;
return { blockedIps };
}
private async matchesAnyReactiveRule(doc: IpIntelligenceDoc): Promise<boolean> {
@@ -287,6 +294,81 @@ export class SecurityPolicyManager {
return `${ip}/${prefix}`;
}
private normalizeNetworkEntries(value: string): string[] {
const trimmed = value.trim();
if (!trimmed) return [];
const cidr = this.normalizeCidr(trimmed);
if (cidr) return [cidr];
const rangeParts = trimmed.split(/\s+-\s+/);
if (rangeParts.length === 2) {
return this.ipv4RangeToCidrs(rangeParts[0], rangeParts[1]);
}
return [];
}
private normalizeNetworkEntryList(values: Array<string | null | undefined>): string[] {
const cidrs = new Set<string>();
for (const value of values) {
if (!value) continue;
for (const entry of value.split(',').map((part) => part.trim()).filter(Boolean)) {
for (const cidr of this.normalizeNetworkEntries(entry)) {
cidrs.add(cidr);
}
}
}
return [...cidrs];
}
private ipv4RangeToCidrs(startIp: string, endIp: string): string[] {
const start = this.ipv4ToBigInt(startIp);
const end = this.ipv4ToBigInt(endIp);
if (start === undefined || end === undefined || start > end) return [];
const cidrs: string[] = [];
let current = start;
while (current <= end) {
let maxBlockSize = current === 0n ? 1n << 32n : current & -current;
const remaining = end - current + 1n;
while (maxBlockSize > remaining) {
maxBlockSize = maxBlockSize / 2n;
}
const prefixLength = 32 - this.powerOfTwoExponent(maxBlockSize);
cidrs.push(`${this.numberToIpv4(current)}/${prefixLength}`);
current += maxBlockSize;
}
return cidrs;
}
private ipv4ToBigInt(ip: string): bigint | undefined {
const normalized = this.normalizeIp(ip);
if (!normalized || plugins.net.isIP(normalized) !== 4) return undefined;
return normalized
.split('.')
.reduce((sum, part) => (sum * 256n) + BigInt(Number(part)), 0n);
}
private numberToIpv4(value: bigint): string {
return [
Number((value >> 24n) & 255n),
Number((value >> 16n) & 255n),
Number((value >> 8n) & 255n),
Number(value & 255n),
].join('.');
}
private powerOfTwoExponent(value: bigint): number {
let exponent = 0;
let remaining = value;
while (remaining > 1n) {
remaining >>= 1n;
exponent++;
}
return exponent;
}
private isPublicIp(ip: string): boolean {
const family = plugins.net.isIP(ip);
if (family === 4) {