Browse Source

add tests

pull/2418/head
Bernd Storath 5 months ago
committed by Ian Foster
parent
commit
327d28945d
  1. 23
      src/server/utils/firewall.ts
  2. 2
      src/server/utils/types.ts
  3. 136
      src/test/unit/firewall.spec.ts

23
src/server/utils/firewall.ts

@ -53,7 +53,11 @@ function parseFirewallEntry(entry: string): ParsedEntry {
if (remaining.startsWith('[')) {
const match = remaining.match(/^\[(.+)\]:(\d+)$/);
if (match) {
return { ip: match[1], port: parseInt(match[2], 10), proto: proto ?? 'both' };
return {
ip: match[1],
port: parseInt(match[2], 10),
proto: proto ?? 'both',
};
}
// Just bracketed IPv6 without port
const ipMatch = remaining.match(/^\[(.+)\]$/);
@ -131,7 +135,9 @@ export const firewall = {
* Initialize the custom chain if it doesn't exist
*/
async initChain(interfaceName: string): Promise<void> {
FW_DEBUG(`Initializing firewall chain ${CHAIN_NAME} for interface ${interfaceName}`);
FW_DEBUG(
`Initializing firewall chain ${CHAIN_NAME} for interface ${interfaceName}`
);
// Create chain if not exists (iptables returns error if exists, so we ignore)
await exec(`iptables -N ${CHAIN_NAME} 2>/dev/null || true`);
@ -169,7 +175,7 @@ export const firewall = {
const effectiveIps =
client.firewallIps && client.firewallIps.length > 0
? client.firewallIps
: client.allowedIps ?? defaultAllowedIps;
: (client.allowedIps ?? defaultAllowedIps);
FW_DEBUG(
`Applying firewall rules for client ${client.name} (${client.id}): ${effectiveIps.join(', ')}`
@ -231,7 +237,11 @@ export const firewall = {
// Apply rules for each enabled client
for (const client of clients) {
if (!client.enabled) continue;
await this.applyClientRules(client, userConfig.defaultAllowedIps, enableIpv6);
await this.applyClientRules(
client,
userConfig.defaultAllowedIps,
enableIpv6
);
}
// Add final DROP for any traffic not explicitly allowed
@ -313,3 +323,8 @@ export const firewall = {
iptablesAvailable = null;
},
};
export const testExports = {
parseFirewallEntry,
generateRuleArgs,
};

2
src/server/utils/types.ts

@ -239,3 +239,5 @@ export function validateZod<T>(
export function assertUnreachable(_: never): never {
throw new Error("Didn't expect to get here");
}
export const testExports = { FirewallIpEntrySchema };

136
src/test/unit/firewall.spec.ts

@ -0,0 +1,136 @@
import { describe, expect, test } from 'vitest';
import { testExports } from '../../server/utils/firewall';
import { testExports as typesTextExports } from '../../server/utils/types';
describe('firewall', () => {
describe('isValidFirewallEntry', () => {
test('invalid ips', () => {
expect(() => typesTextExports.FirewallIpEntrySchema.parse('')).toThrow();
expect(() =>
typesTextExports.FirewallIpEntrySchema.parse('255.255.255.256')
).toThrow();
expect(() =>
typesTextExports.FirewallIpEntrySchema.parse('1.1.1.256')
).toThrow();
expect(() =>
typesTextExports.FirewallIpEntrySchema.parse('1.1.1.1.1')
).toThrow();
expect(() =>
typesTextExports.FirewallIpEntrySchema.parse('[]:443/udp')
).toThrow();
expect(() =>
typesTextExports.FirewallIpEntrySchema.parse('[]:443')
).toThrow();
expect(() =>
typesTextExports.FirewallIpEntrySchema.parse('[::1]/32')
).toThrow();
expect(() =>
typesTextExports.FirewallIpEntrySchema.parse('[1.1.1.1]/32')
).toThrow();
expect(() =>
typesTextExports.FirewallIpEntrySchema.parse('[::g]/32')
).toThrow();
expect(() =>
typesTextExports.FirewallIpEntrySchema.parse('2001:dbx::1')
).toThrow();
});
test('invalid port, protocol or cidr', () => {
expect(() =>
typesTextExports.FirewallIpEntrySchema.parse('1.1.1.1:80/tcpp')
).toThrow();
expect(() =>
typesTextExports.FirewallIpEntrySchema.parse('1.1.1.1:65536')
).toThrow();
expect(() =>
typesTextExports.FirewallIpEntrySchema.parse('1.1.1.1:0')
).toThrow();
expect(() =>
typesTextExports.FirewallIpEntrySchema.parse('1.1.1.1/33')
).toThrow();
expect(() =>
typesTextExports.FirewallIpEntrySchema.parse('1.1.1.1/32:0')
).toThrow();
});
test('protocol without port', () => {
expect(() =>
typesTextExports.FirewallIpEntrySchema.parse('1.1.1.1/tcp')
).toThrow();
});
test('valid entries', () => {
expect(typesTextExports.FirewallIpEntrySchema.parse('1.1.1.1')).toBe(
'1.1.1.1'
);
expect(typesTextExports.FirewallIpEntrySchema.parse('::/0')).toBe('::/0');
expect(typesTextExports.FirewallIpEntrySchema.parse('::0/0')).toBe(
'::0/0'
);
expect(typesTextExports.FirewallIpEntrySchema.parse('2001:db8::1')).toBe(
'2001:db8::1'
);
expect(typesTextExports.FirewallIpEntrySchema.parse('::1')).toBe('::1');
expect(
typesTextExports.FirewallIpEntrySchema.parse('2001:db8::1/32')
).toBe('2001:db8::1/32');
expect(typesTextExports.FirewallIpEntrySchema.parse('[::1]')).toEqual({
ip: '[::1]',
});
});
});
describe('parseFirewallEntry', () => {
test('IPv4 with port and protocol', () => {
expect(() => testExports.parseFirewallEntry('1.1.1.1/tcp')).toThrow();
expect(() => testExports.parseFirewallEntry('1.1.1.1/udp')).toThrow();
expect(testExports.parseFirewallEntry('1.1.1.1')).toEqual({
ip: '1.1.1.1',
});
expect(testExports.parseFirewallEntry('1.1.1.1:80')).toEqual({
ip: '1.1.1.1',
port: 80,
proto: 'both',
});
expect(testExports.parseFirewallEntry('1.1.1.1:80/tcp')).toEqual({
ip: '1.1.1.1',
port: 80,
proto: 'tcp',
});
expect(testExports.parseFirewallEntry('1.1.1.1:80/udp')).toEqual({
ip: '1.1.1.1',
port: 80,
proto: 'udp',
});
});
test('IPv6 with port and protocol', () => {
expect(() =>
testExports.parseFirewallEntry('[2001:db8::1]/tcp')
).toThrow();
expect(() =>
testExports.parseFirewallEntry('[2001:db8::1]/udp')
).toThrow();
expect(testExports.parseFirewallEntry('[2001:db8::1]')).toEqual({
ip: '2001:db8::1',
});
expect(testExports.parseFirewallEntry('[2001:db8::1]:443')).toEqual({
ip: '2001:db8::1',
port: 443,
proto: 'both',
});
expect(testExports.parseFirewallEntry('[2001:db8::1]:443/tcp')).toEqual({
ip: '2001:db8::1',
port: 443,
proto: 'tcp',
});
expect(testExports.parseFirewallEntry('[2001:db8::1]:443/udp')).toEqual({
ip: '2001:db8::1',
port: 443,
proto: 'udp',
});
expect(testExports.parseFirewallEntry('::0/0')).toEqual({
ip: '::0/0',
});
expect(() => testExports.parseFirewallEntry('::0/0/tcp')).toThrow();
});
});
});
Loading…
Cancel
Save