Browse Source

improve tests, typechecking, documentation

pull/2418/head
Bernd Storath 5 months ago
parent
commit
ddc1b766b9
  1. 13
      docs/content/guides/clients.md
  2. 10
      src/server/utils/types.ts
  3. 123
      src/test/unit/firewall.spec.ts

13
docs/content/guides/clients.md

@ -35,8 +35,8 @@ Unlike "Allowed IPs" which only controls routing on the client side, these rules
**Supported Formats:** **Supported Formats:**
- `10.10.0.3` - Allow access to a single IP address - `10.10.0.3`, `2001:db8::1` - Allow access to a single IP address
- `10.10.0.0/24` - Allow access to an entire subnet - `10.10.0.0/24`, `2001:db8::/32` - Allow access to an entire subnet
- `192.168.1.5:443` - Allow access to specific port (TCP+UDP) - `192.168.1.5:443` - Allow access to specific port (TCP+UDP)
- `192.168.1.5:443/tcp` - Allow access to specific port (TCP only) - `192.168.1.5:443/tcp` - Allow access to specific port (TCP only)
- `192.168.1.5:443/udp` - Allow access to specific port (UDP only) - `192.168.1.5:443/udp` - Allow access to specific port (UDP only)
@ -62,10 +62,11 @@ Protocol specifiers (`/tcp` or `/udp`) require a port number. The following form
- **Disable for specific client**: To disable firewall filtering for a single client while keeping it enabled for others, add `0.0.0.0/0, ::/0` to allow all traffic - **Disable for specific client**: To disable firewall filtering for a single client while keeping it enabled for others, add `0.0.0.0/0, ::/0` to allow all traffic
**Use Case Examples**: **Use Case Examples**:
- Allow only specific servers: `10.10.0.5`
- Allow only internal network: `10.10.0.0/24, 192.168.1.0/24` - Allow only specific servers: `10.10.0.5`
- Allow only web browsing: `0.0.0.0/0:80, 0.0.0.0/0:443, ::/0:80, ::/0:443` - Allow only internal network: `10.10.0.0/24, 192.168.1.0/24`
- Block internet, allow LAN: Leave "Allowed IPs" as `0.0.0.0/0, ::/0` but set Firewall IPs to `10.0.0.0/8, 192.168.0.0/16` - Allow only web browsing: `0.0.0.0/0:80, 0.0.0.0/0:443, [::/0]:80, [::/0]:443`
- Block internet, allow LAN: Leave "Allowed IPs" as `0.0.0.0/0, ::/0` but set Firewall IPs to `10.0.0.0/8, 192.168.0.0/16`
## Server Allowed IPs ## Server Allowed IPs

10
src/server/utils/types.ts

@ -111,8 +111,8 @@ const FirewallIpEntrySchema = z
return false; return false;
} }
const [, ipPart, portPart] = portMatch; const [, ipPart, portPart] = portMatch;
const port = parseInt(portPart, 10); const port = parseInt(portPart!, 10);
const cleanIp = ipPart.replace(/^\[|\]$/g, ''); const cleanIp = ipPart!.replace(/^\[|\]$/g, '');
return (isIP(cleanIp) || isCidr(cleanIp)) && port >= 1 && port <= 65535; return (isIP(cleanIp) || isCidr(cleanIp)) && port >= 1 && port <= 65535;
} }
@ -125,17 +125,17 @@ const FirewallIpEntrySchema = z
const bracketedMatch = entryWithoutProto.match(/^\[(.+)\]$/); const bracketedMatch = entryWithoutProto.match(/^\[(.+)\]$/);
if (bracketedMatch) { if (bracketedMatch) {
const innerIp = bracketedMatch[1]; const innerIp = bracketedMatch[1];
return isIP(innerIp) || isCidr(innerIp); return isIP(innerIp!) || isCidr(innerIp!);
} }
// Check if it's IP:port format (IPv4:port or [IPv6]:port) // Check if it's IP:port format (IPv4:port or [IPv6]:port)
const portMatch = entryWithoutProto.match(/^(.+):(\d+)$/); const portMatch = entryWithoutProto.match(/^(.+):(\d+)$/);
if (portMatch) { if (portMatch) {
const [, ipPart, portPart] = portMatch; const [, ipPart, portPart] = portMatch;
const port = parseInt(portPart, 10); const port = parseInt(portPart!, 10);
// Remove IPv6 brackets if present // Remove IPv6 brackets if present
const cleanIp = ipPart.replace(/^\[|\]$/g, ''); const cleanIp = ipPart!.replace(/^\[|\]$/g, '');
// Validate IP and port // Validate IP and port
return (isIP(cleanIp) || isCidr(cleanIp)) && port >= 1 && port <= 65535; return (isIP(cleanIp) || isCidr(cleanIp)) && port >= 1 && port <= 65535;

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

@ -75,27 +75,43 @@ describe('firewall', () => {
expect(typesTestExports.FirewallIpEntrySchema.parse('[::1]')).toBe( expect(typesTestExports.FirewallIpEntrySchema.parse('[::1]')).toBe(
'[::1]' '[::1]'
); );
expect(firewallTestExports.parseFirewallEntry('[::1]')).toEqual({ expect(typesTestExports.FirewallIpEntrySchema.parse('[::1/32]')).toBe(
ip: '::1', '[::1/32]'
}); );
}); });
}); });
describe('parseFirewallEntry', () => { describe('parseFirewallEntry', () => {
test('IPv4 with port and protocol', () => { test('IPv4', () => {
expect(firewallTestExports.parseFirewallEntry('1.1.1.1')).toEqual({
ip: '1.1.1.1',
});
});
test('IPv4 with Protocol', () => {
expect(() => expect(() =>
firewallTestExports.parseFirewallEntry('1.1.1.1/tcp') firewallTestExports.parseFirewallEntry('1.1.1.1/tcp')
).toThrow(); ).toThrow();
expect(() => expect(() =>
firewallTestExports.parseFirewallEntry('1.1.1.1/udp') firewallTestExports.parseFirewallEntry('1.1.1.1/udp')
).toThrow(); ).toThrow();
expect(firewallTestExports.parseFirewallEntry('1.1.1.1')).toEqual({ });
ip: '1.1.1.1', 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({ expect(firewallTestExports.parseFirewallEntry('1.1.1.1:80')).toEqual({
ip: '1.1.1.1', ip: '1.1.1.1',
port: 80, port: 80,
proto: 'both', proto: 'both',
}); });
});
test('IPv4 with Port and Protocol', () => {
expect(firewallTestExports.parseFirewallEntry('1.1.1.1:80/tcp')).toEqual({ expect(firewallTestExports.parseFirewallEntry('1.1.1.1:80/tcp')).toEqual({
ip: '1.1.1.1', ip: '1.1.1.1',
port: 80, port: 80,
@ -107,17 +123,61 @@ describe('firewall', () => {
proto: 'udp', proto: 'udp',
}); });
}); });
test('IPv4 with CIDR and Port', () => {
test('IPv6 with port and protocol', () => { 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(() => expect(() =>
firewallTestExports.parseFirewallEntry('[2001:db8::1]/tcp') firewallTestExports.parseFirewallEntry('[2001:db8::1]/tcp')
).toThrow(); ).toThrow();
expect(() => expect(() =>
firewallTestExports.parseFirewallEntry('[2001:db8::1]/udp') firewallTestExports.parseFirewallEntry('2001:db8::1/udp')
).toThrow(); ).toThrow();
expect(firewallTestExports.parseFirewallEntry('[2001:db8::1]')).toEqual({ });
ip: '2001:db8::1', 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( expect(
firewallTestExports.parseFirewallEntry('[2001:db8::1]:443') firewallTestExports.parseFirewallEntry('[2001:db8::1]:443')
).toEqual({ ).toEqual({
@ -125,6 +185,8 @@ describe('firewall', () => {
port: 443, port: 443,
proto: 'both', proto: 'both',
}); });
});
test('IPv6 with Port and Protocol', () => {
expect( expect(
firewallTestExports.parseFirewallEntry('[2001:db8::1]:443/tcp') firewallTestExports.parseFirewallEntry('[2001:db8::1]:443/tcp')
).toEqual({ ).toEqual({
@ -139,13 +201,24 @@ describe('firewall', () => {
port: 443, port: 443,
proto: 'udp', proto: 'udp',
}); });
});
expect(firewallTestExports.parseFirewallEntry('::0/0')).toEqual({ test('IPv6 with CIDR and Port', () => {
ip: '::0/0', 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',
}); });
expect(() =>
firewallTestExports.parseFirewallEntry('::0/0/tcp')
).toThrow();
}); });
}); });
describe('sanitizeComment', () => { describe('sanitizeComment', () => {
@ -189,11 +262,21 @@ describe('firewall', () => {
]); ]);
}); });
test('omits comment when not provided', () => { test('omits comment when not provided', () => {
const rules = firewallTestExports.generateRuleArgs('10.8.0.2', { const rulesTcp = firewallTestExports.generateRuleArgs('10.8.0.2', {
ip: '10.0.0.1', ip: '10.0.0.1',
port: 80,
proto: 'tcp',
}); });
expect(rules).toEqual([ expect(rulesTcp).toEqual([
'-A WG_CLIENTS -s 10.8.0.2 -d 10.0.0.1 -j ACCEPT', '-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', () => { test('comment with port generates two rules for both proto', () => {

Loading…
Cancel
Save