157 lines
4.6 KiB
Python
157 lines
4.6 KiB
Python
from odoo import models, fields, api
|
|
from odoo.exceptions import ValidationError
|
|
import re
|
|
import ipaddress
|
|
|
|
class FirewallRuleTemplate(models.Model):
|
|
_name = 'unifi.firewall.rule.template'
|
|
_description = 'Firewall Rule Template'
|
|
_order = 'rule_index'
|
|
|
|
name = fields.Char(
|
|
string='Template Name',
|
|
required=True
|
|
)
|
|
|
|
ctrl_id = fields.Many2one(
|
|
'unifi.ctrl',
|
|
string='Controller',
|
|
required=True,
|
|
help="Controller where this rule is applied."
|
|
)
|
|
|
|
enabled = fields.Boolean(
|
|
string='Enabled',
|
|
default=True,
|
|
help="Whether this rule is active"
|
|
)
|
|
|
|
ruleset = fields.Selection([
|
|
('LAN_IN', 'LAN In'),
|
|
('LAN_OUT', 'LAN Out'),
|
|
('LAN_LOCAL', 'LAN Local'),
|
|
('WAN_IN', 'WAN In'),
|
|
('WAN_OUT', 'WAN Out'),
|
|
('WAN_LOCAL', 'WAN Local')
|
|
], string='Rule Set', required=True, default='LAN_IN')
|
|
|
|
rule_index = fields.Integer(
|
|
string='Rule Index',
|
|
help="Order in which the rule is applied",
|
|
default=2000
|
|
)
|
|
|
|
action = fields.Selection(
|
|
selection=[
|
|
('accept', 'Accept'),
|
|
('drop', 'Drop'),
|
|
('reject', 'Reject')
|
|
],
|
|
string='Action',
|
|
required=True
|
|
)
|
|
|
|
src_ip = fields.Char(
|
|
string='Source IP',
|
|
help="Source IP address or network (e.g., 192.168.1.0/24)"
|
|
)
|
|
|
|
dst_ip = fields.Char(
|
|
string='Destination IP',
|
|
help="Destination IP address or network (e.g., 192.168.1.0/24)"
|
|
)
|
|
|
|
protocol = fields.Selection(
|
|
selection=[
|
|
('all', 'All'),
|
|
('tcp', 'TCP'),
|
|
('udp', 'UDP'),
|
|
('icmp', 'ICMP')
|
|
],
|
|
string='Protocol',
|
|
required=True,
|
|
default='all'
|
|
)
|
|
|
|
port = fields.Char(
|
|
string='Port',
|
|
help="Port or port range (e.g., 80 or 8000-9000)"
|
|
)
|
|
|
|
firewall_group_id = fields.Many2one(
|
|
'unifi.firewall.group',
|
|
string='Firewall Group',
|
|
help="Associated firewall group"
|
|
)
|
|
|
|
categories = fields.Many2many(
|
|
'unifi.dpi.category',
|
|
string='DPI Categories',
|
|
help="Deep Packet Inspection categories this rule applies to"
|
|
)
|
|
|
|
@api.constrains('port')
|
|
def _check_port(self):
|
|
"""Validate port format."""
|
|
for record in self:
|
|
if not record.port:
|
|
continue
|
|
|
|
# Check single port
|
|
if record.port.isdigit():
|
|
port = int(record.port)
|
|
if not (1 <= port <= 65535):
|
|
raise ValidationError("Port must be between 1 and 65535")
|
|
continue
|
|
|
|
# Check port range
|
|
if '-' in record.port:
|
|
try:
|
|
start, end = map(int, record.port.split('-'))
|
|
if not (1 <= start <= end <= 65535):
|
|
raise ValidationError("Port range must be between 1 and 65535")
|
|
except ValueError:
|
|
raise ValidationError("Invalid port range format. Use format: start-end")
|
|
else:
|
|
raise ValidationError("Invalid port format. Use a single port or range (e.g., 80 or 8000-9000)")
|
|
|
|
@api.constrains('src_ip', 'dst_ip')
|
|
def _check_ip_addresses(self):
|
|
"""Validate IP address formats."""
|
|
for record in self:
|
|
for field in [record.src_ip, record.dst_ip]:
|
|
if not field:
|
|
continue
|
|
try:
|
|
# Handle CIDR notation
|
|
if '/' in field:
|
|
ipaddress.ip_network(field)
|
|
else:
|
|
ipaddress.ip_address(field)
|
|
except ValueError:
|
|
raise ValidationError(f"Invalid IP address format: {field}")
|
|
|
|
def copy_to_controller(self):
|
|
"""Copy this template to the actual firewall rules on the controller."""
|
|
self.ensure_one()
|
|
client = self.ctrl_id.get_client()
|
|
|
|
rule_data = {
|
|
'name': self.name,
|
|
'enabled': self.enabled,
|
|
'ruleset': self.ruleset,
|
|
'rule_index': self.rule_index,
|
|
'protocol': self.protocol,
|
|
'action': self.action,
|
|
}
|
|
|
|
if self.port:
|
|
rule_data['port'] = self.port
|
|
if self.src_ip:
|
|
rule_data['src'] = self.src_ip
|
|
if self.dst_ip:
|
|
rule_data['dst'] = self.dst_ip
|
|
if self.categories:
|
|
rule_data['categories'] = self.categories.mapped('category_id')
|
|
|
|
return client.create_firewall_rule(rule_data)
|