174 lines
4.2 KiB
TypeScript
174 lines
4.2 KiB
TypeScript
import type { TNftFamily, INftFirewallRule, INftIPSetConfig } from './nft.types.js';
|
|
|
|
/**
|
|
* Build an nft firewall rule for input/output/forward chains.
|
|
*/
|
|
export function buildFirewallRule(
|
|
tableName: string,
|
|
family: TNftFamily,
|
|
rule: INftFirewallRule,
|
|
): string[] {
|
|
const chain = rule.direction;
|
|
const parts: string[] = [`nft add rule ${family} ${tableName} ${chain}`];
|
|
|
|
// Connection tracking states
|
|
if (rule.ctStates && rule.ctStates.length > 0) {
|
|
parts.push(`ct state { ${rule.ctStates.join(', ')} }`);
|
|
}
|
|
|
|
// Protocol and port matching
|
|
const protocols = expandProtocols(rule.protocol);
|
|
const commands: string[] = [];
|
|
|
|
for (const proto of protocols) {
|
|
const ruleParts = [...parts];
|
|
|
|
if (rule.protocol) {
|
|
ruleParts.push(proto);
|
|
}
|
|
|
|
if (rule.sourceIP) {
|
|
ruleParts.push(`ip saddr ${rule.sourceIP}`);
|
|
}
|
|
|
|
if (rule.destIP) {
|
|
ruleParts.push(`ip daddr ${rule.destIP}`);
|
|
}
|
|
|
|
if (rule.sourcePort != null) {
|
|
ruleParts.push(`${proto} sport ${rule.sourcePort}`);
|
|
}
|
|
|
|
if (rule.destPort != null) {
|
|
// If protocol wasn't explicitly set but we have a port, we need the protocol
|
|
if (!rule.protocol) {
|
|
ruleParts.push(`tcp dport ${rule.destPort}`);
|
|
} else {
|
|
ruleParts.push(`${proto} dport ${rule.destPort}`);
|
|
}
|
|
}
|
|
|
|
if (rule.comment) {
|
|
ruleParts.push(`comment "${rule.comment}"`);
|
|
}
|
|
|
|
ruleParts.push(rule.action);
|
|
commands.push(ruleParts.join(' '));
|
|
}
|
|
|
|
// If no protocol expansion needed (no protocol-specific fields)
|
|
if (commands.length === 0) {
|
|
const ruleParts = [...parts];
|
|
if (rule.sourceIP) {
|
|
ruleParts.push(`ip saddr ${rule.sourceIP}`);
|
|
}
|
|
if (rule.destIP) {
|
|
ruleParts.push(`ip daddr ${rule.destIP}`);
|
|
}
|
|
if (rule.comment) {
|
|
ruleParts.push(`comment "${rule.comment}"`);
|
|
}
|
|
ruleParts.push(rule.action);
|
|
commands.push(ruleParts.join(' '));
|
|
}
|
|
|
|
return commands;
|
|
}
|
|
|
|
/**
|
|
* Build commands to create an nft named set (IP set).
|
|
*/
|
|
export function buildIPSetCreate(
|
|
tableName: string,
|
|
family: TNftFamily,
|
|
config: INftIPSetConfig,
|
|
): string[] {
|
|
const commands: string[] = [];
|
|
|
|
// Create the set
|
|
commands.push(
|
|
`nft add set ${family} ${tableName} ${config.name} { type ${config.type} \\; }`
|
|
);
|
|
|
|
// Add initial elements if provided
|
|
if (config.elements && config.elements.length > 0) {
|
|
commands.push(
|
|
`nft add element ${family} ${tableName} ${config.name} { ${config.elements.join(', ')} }`
|
|
);
|
|
}
|
|
|
|
return commands;
|
|
}
|
|
|
|
/**
|
|
* Build command to add elements to an existing set.
|
|
*/
|
|
export function buildIPSetAddElements(
|
|
tableName: string,
|
|
family: TNftFamily,
|
|
setName: string,
|
|
elements: string[],
|
|
): string[] {
|
|
if (elements.length === 0) return [];
|
|
return [
|
|
`nft add element ${family} ${tableName} ${setName} { ${elements.join(', ')} }`
|
|
];
|
|
}
|
|
|
|
/**
|
|
* Build command to remove elements from a set.
|
|
*/
|
|
export function buildIPSetRemoveElements(
|
|
tableName: string,
|
|
family: TNftFamily,
|
|
setName: string,
|
|
elements: string[],
|
|
): string[] {
|
|
if (elements.length === 0) return [];
|
|
return [
|
|
`nft delete element ${family} ${tableName} ${setName} { ${elements.join(', ')} }`
|
|
];
|
|
}
|
|
|
|
/**
|
|
* Build command to delete an entire set.
|
|
*/
|
|
export function buildIPSetDelete(
|
|
tableName: string,
|
|
family: TNftFamily,
|
|
setName: string,
|
|
): string[] {
|
|
return [
|
|
`nft delete set ${family} ${tableName} ${setName}`
|
|
];
|
|
}
|
|
|
|
/**
|
|
* Build a rule that matches against a named set.
|
|
*/
|
|
export function buildIPSetMatchRule(
|
|
tableName: string,
|
|
family: TNftFamily,
|
|
options: {
|
|
setName: string;
|
|
direction: 'input' | 'output' | 'forward';
|
|
matchField: 'saddr' | 'daddr';
|
|
action: 'accept' | 'drop' | 'reject';
|
|
},
|
|
): string[] {
|
|
return [
|
|
`nft add rule ${family} ${tableName} ${options.direction} ip ${options.matchField} @${options.setName} ${options.action}`
|
|
];
|
|
}
|
|
|
|
// ─── Internal helpers ─────────────────────────────────────────────
|
|
|
|
function expandProtocols(protocol?: 'tcp' | 'udp' | 'both'): string[] {
|
|
if (!protocol) return [];
|
|
switch (protocol) {
|
|
case 'tcp': return ['tcp'];
|
|
case 'udp': return ['udp'];
|
|
case 'both': return ['tcp', 'udp'];
|
|
}
|
|
}
|