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:**
- `10.10.0.3` - Allow access to a single IP address
- `10.10.0.0/24` - Allow access to an entire subnet
- `10.10.0.3`, `2001:db8::1` - Allow access to a single IP address
- `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/tcp` - Allow access to specific port (TCP 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
**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 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`
- Allow only specific servers: `10.10.0.5`
- Allow only internal network: `10.10.0.0/24, 192.168.1.0/24`
- 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

10
src/server/utils/types.ts

@ -111,8 +111,8 @@ const FirewallIpEntrySchema = z
return false;
}
const [, ipPart, portPart] = portMatch;
const port = parseInt(portPart, 10);
const cleanIp = ipPart.replace(/^\[|\]$/g, '');
const port = parseInt(portPart!, 10);
const cleanIp = ipPart!.replace(/^\[|\]$/g, '');
return (isIP(cleanIp) || isCidr(cleanIp)) && port >= 1 && port <= 65535;
}
@ -125,17 +125,17 @@ const FirewallIpEntrySchema = z
const bracketedMatch = entryWithoutProto.match(/^\[(.+)\]$/);
if (bracketedMatch) {
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)
const portMatch = entryWithoutProto.match(/^(.+):(\d+)$/);
if (portMatch) {
const [, ipPart, portPart] = portMatch;
const port = parseInt(portPart, 10);
const port = parseInt(portPart!, 10);
// Remove IPv6 brackets if present
const cleanIp = ipPart.replace(/^\[|\]$/g, '');
const cleanIp = ipPart!.replace(/^\[|\]$/g, '');
// Validate IP and port
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(
'[::1]'
);
expect(firewallTestExports.parseFirewallEntry('[::1]')).toEqual({
ip: '::1',
});
expect(typesTestExports.FirewallIpEntrySchema.parse('[::1/32]')).toBe(
'[::1/32]'
);
});
});
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(() =>
firewallTestExports.parseFirewallEntry('1.1.1.1/tcp')
).toThrow();
expect(() =>
firewallTestExports.parseFirewallEntry('1.1.1.1/udp')
).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({
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,
@ -107,17 +123,61 @@ describe('firewall', () => {
proto: 'udp',
});
});
test('IPv6 with port and protocol', () => {
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')
firewallTestExports.parseFirewallEntry('2001:db8::1/udp')
).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(
firewallTestExports.parseFirewallEntry('[2001:db8::1]:443')
).toEqual({
@ -125,6 +185,8 @@ describe('firewall', () => {
port: 443,
proto: 'both',
});
});
test('IPv6 with Port and Protocol', () => {
expect(
firewallTestExports.parseFirewallEntry('[2001:db8::1]:443/tcp')
).toEqual({
@ -139,13 +201,24 @@ describe('firewall', () => {
port: 443,
proto: 'udp',
});
expect(firewallTestExports.parseFirewallEntry('::0/0')).toEqual({
ip: '::0/0',
});
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',
});
expect(() =>
firewallTestExports.parseFirewallEntry('::0/0/tcp')
).toThrow();
});
});
describe('sanitizeComment', () => {
@ -189,11 +262,21 @@ describe('firewall', () => {
]);
});
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',
port: 80,
proto: 'tcp',
});
expect(rules).toEqual([
'-A WG_CLIENTS -s 10.8.0.2 -d 10.0.0.1 -j ACCEPT',
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', () => {

Loading…
Cancel
Save