You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

295 lines
9.3 KiB

import { describe, expect, test } from 'vitest';
import { firewallTestExports } from '#server/utils/firewall';
import { typesTestExports } from '#server/utils/types';
describe('firewall', () => {
describe('isValidFirewallEntry', () => {
test('invalid ips', () => {
expect(() => typesTestExports.FirewallIpEntrySchema.parse('')).toThrow();
expect(() =>
typesTestExports.FirewallIpEntrySchema.parse('255.255.255.256')
).toThrow();
expect(() =>
typesTestExports.FirewallIpEntrySchema.parse('1.1.1.256')
).toThrow();
expect(() =>
typesTestExports.FirewallIpEntrySchema.parse('1.1.1.1.1')
).toThrow();
expect(() =>
typesTestExports.FirewallIpEntrySchema.parse('[]:443/udp')
).toThrow();
expect(() =>
typesTestExports.FirewallIpEntrySchema.parse('[]:443')
).toThrow();
expect(() =>
typesTestExports.FirewallIpEntrySchema.parse('[::1]/32')
).toThrow();
expect(() =>
typesTestExports.FirewallIpEntrySchema.parse('[1.1.1.1]/32')
).toThrow();
expect(() =>
typesTestExports.FirewallIpEntrySchema.parse('[::g]/32')
).toThrow();
expect(() =>
typesTestExports.FirewallIpEntrySchema.parse('2001:dbx::1')
).toThrow();
});
test('invalid port, protocol or cidr', () => {
expect(() =>
typesTestExports.FirewallIpEntrySchema.parse('1.1.1.1:80/tcpp')
).toThrow();
expect(() =>
typesTestExports.FirewallIpEntrySchema.parse('1.1.1.1:65536')
).toThrow();
expect(() =>
typesTestExports.FirewallIpEntrySchema.parse('1.1.1.1:0')
).toThrow();
expect(() =>
typesTestExports.FirewallIpEntrySchema.parse('1.1.1.1/33')
).toThrow();
expect(() =>
typesTestExports.FirewallIpEntrySchema.parse('1.1.1.1/32:0')
).toThrow();
});
test('protocol without port', () => {
expect(() =>
typesTestExports.FirewallIpEntrySchema.parse('1.1.1.1/tcp')
).toThrow();
});
test('valid entries', () => {
expect(typesTestExports.FirewallIpEntrySchema.parse('1.1.1.1')).toBe(
'1.1.1.1'
);
expect(typesTestExports.FirewallIpEntrySchema.parse('::/0')).toBe('::/0');
expect(typesTestExports.FirewallIpEntrySchema.parse('::0/0')).toBe(
'::0/0'
);
expect(typesTestExports.FirewallIpEntrySchema.parse('2001:db8::1')).toBe(
'2001:db8::1'
);
expect(typesTestExports.FirewallIpEntrySchema.parse('::1')).toBe('::1');
expect(
typesTestExports.FirewallIpEntrySchema.parse('2001:db8::1/32')
).toBe('2001:db8::1/32');
expect(typesTestExports.FirewallIpEntrySchema.parse('[::1]')).toBe(
'[::1]'
);
expect(typesTestExports.FirewallIpEntrySchema.parse('[::1/32]')).toBe(
'[::1/32]'
);
});
});
describe('parseFirewallEntry', () => {
test('IPv4', () => {
expect(firewallTestExports.parseFirewallEntry('1.1.1.1')).toEqual({
ip: '1.1.1.1',
});
});
test('IPv4 with Protocol', () => {
expect(() =>
firewallTestExports.parseFirewallEntry('1.1.1.1/tcp')
).toThrow();
expect(() =>
firewallTestExports.parseFirewallEntry('1.1.1.1/udp')
).toThrow();
});
test('IPv4 with CIDR', () => {
expect(firewallTestExports.parseFirewallEntry('1.1.1.1/32')).toEqual({
ip: '1.1.1.1/32',
});
});
test('IPv4 with CIDR and Protocol', () => {
expect(() =>
firewallTestExports.parseFirewallEntry('1.1.1.1/32/tcp')
).toThrow();
});
test('IPv4 with Port', () => {
expect(firewallTestExports.parseFirewallEntry('1.1.1.1:80')).toEqual({
ip: '1.1.1.1',
port: 80,
proto: 'both',
});
});
test('IPv4 with Port and Protocol', () => {
expect(firewallTestExports.parseFirewallEntry('1.1.1.1:80/tcp')).toEqual({
ip: '1.1.1.1',
port: 80,
proto: 'tcp',
});
expect(firewallTestExports.parseFirewallEntry('1.1.1.1:80/udp')).toEqual({
ip: '1.1.1.1',
port: 80,
proto: 'udp',
});
});
test('IPv4 with CIDR and Port', () => {
expect(
firewallTestExports.parseFirewallEntry('10.10.0.0/24:443')
).toEqual({
ip: '10.10.0.0/24',
port: 443,
proto: 'both',
});
});
test('IPv4 with CIDR, Port and Protocol', () => {
expect(
firewallTestExports.parseFirewallEntry('10.10.0.0/24:443/tcp')
).toEqual({
ip: '10.10.0.0/24',
port: 443,
proto: 'tcp',
});
expect(
firewallTestExports.parseFirewallEntry('10.10.0.0/24:443/udp')
).toEqual({
ip: '10.10.0.0/24',
port: 443,
proto: 'udp',
});
});
test('IPv6', () => {
expect(firewallTestExports.parseFirewallEntry('[2001:db8::1]')).toEqual({
ip: '2001:db8::1',
});
expect(firewallTestExports.parseFirewallEntry('2001:db8::1')).toEqual({
ip: '2001:db8::1',
});
});
test('IPv6 with Protocol', () => {
expect(() =>
firewallTestExports.parseFirewallEntry('[2001:db8::1]/tcp')
).toThrow();
expect(() =>
firewallTestExports.parseFirewallEntry('2001:db8::1/udp')
).toThrow();
});
test('IPv6 with CIDR', () => {
expect(firewallTestExports.parseFirewallEntry('::0/0')).toEqual({
ip: '::0/0',
});
expect(firewallTestExports.parseFirewallEntry('[::0/0]')).toEqual({
ip: '::0/0',
});
});
test('IPv6 with CIDR and Protocol', () => {
expect(() =>
firewallTestExports.parseFirewallEntry('::0/0/tcp')
).toThrow();
});
test('IPv6 with Port', () => {
expect(
firewallTestExports.parseFirewallEntry('[2001:db8::1]:443')
).toEqual({
ip: '2001:db8::1',
port: 443,
proto: 'both',
});
});
test('IPv6 with Port and Protocol', () => {
expect(
firewallTestExports.parseFirewallEntry('[2001:db8::1]:443/tcp')
).toEqual({
ip: '2001:db8::1',
port: 443,
proto: 'tcp',
});
expect(
firewallTestExports.parseFirewallEntry('[2001:db8::1]:443/udp')
).toEqual({
ip: '2001:db8::1',
port: 443,
proto: 'udp',
});
});
test('IPv6 with CIDR and Port', () => {
expect(
firewallTestExports.parseFirewallEntry('[2001:db8::/32]:443')
).toEqual({
ip: '2001:db8::/32',
port: 443,
proto: 'both',
});
});
test('IPv6 with CIDR, Port and Protocol', () => {
expect(
firewallTestExports.parseFirewallEntry('[2001:db8::/32]:443/tcp')
).toEqual({
ip: '2001:db8::/32',
port: 443,
proto: 'tcp',
});
});
});
describe('sanitizeComment', () => {
test('basic ASCII name', () => {
expect(firewallTestExports.sanitizeComment(1, 'My Laptop')).toBe(
'client 1: My Laptop'
);
});
test('strips non-ASCII and shell metacharacters', () => {
expect(firewallTestExports.sanitizeComment(42, 'café')).toBe(
'client 42: caf'
);
expect(firewallTestExports.sanitizeComment(5, 'a"; rm -rf /')).toBe(
'client 5: a rm -rf '
);
expect(firewallTestExports.sanitizeComment(7, 'test$(cmd)`id`')).toBe(
'client 7: testcmdid'
);
});
test('preserves allowed punctuation', () => {
expect(firewallTestExports.sanitizeComment(3, 'phone-2.lan_home')).toBe(
'client 3: phone-2.lan_home'
);
});
test('truncates to 256 bytes', () => {
const longName = 'a'.repeat(300);
const result = firewallTestExports.sanitizeComment(1, longName);
expect(result.length).toBeLessThanOrEqual(256);
expect(result).toBe('client 1: ' + 'a'.repeat(246));
});
});
describe('generateRuleArgs', () => {
test('includes comment when provided', () => {
const rules = firewallTestExports.generateRuleArgs(
'10.8.0.2',
{ ip: '10.0.0.1' },
'client 1: test'
);
expect(rules).toEqual([
'-A WG_CLIENTS -s 10.8.0.2 -d 10.0.0.1 -m comment --comment "client 1: test" -j ACCEPT',
]);
});
test('omits comment when not provided', () => {
const rulesTcp = firewallTestExports.generateRuleArgs('10.8.0.2', {
ip: '10.0.0.1',
port: 80,
proto: 'tcp',
});
expect(rulesTcp).toEqual([
'-A WG_CLIENTS -s 10.8.0.2 -d 10.0.0.1 -p tcp --dport 80 -j ACCEPT',
]);
const rulesUdp = firewallTestExports.generateRuleArgs('10.8.0.2', {
ip: '10.0.0.1',
port: 80,
proto: 'udp',
});
expect(rulesUdp).toEqual([
'-A WG_CLIENTS -s 10.8.0.2 -d 10.0.0.1 -p udp --dport 80 -j ACCEPT',
]);
});
test('comment with port generates two rules for both proto', () => {
const rules = firewallTestExports.generateRuleArgs(
'10.8.0.2',
{ ip: '10.0.0.1', port: 443, proto: 'both' },
'client 2: phone'
);
expect(rules).toEqual([
'-A WG_CLIENTS -s 10.8.0.2 -d 10.0.0.1 -p tcp --dport 443 -m comment --comment "client 2: phone" -j ACCEPT',
'-A WG_CLIENTS -s 10.8.0.2 -d 10.0.0.1 -p udp --dport 443 -m comment --comment "client 2: phone" -j ACCEPT',
]);
});
});
});