def add_firewall_rule(fw_rule): # ensuring all necessary fields are present in the namespace before continuing. valid_fields = [ 'action', 'dst_ip', 'dst_netmask', 'dst_port', 'position', 'protocol', 'src_ip', 'src_netmask', 'tab', 'zone' ] if not all([hasattr(fw_rule, x) for x in valid_fields]): raise ValidationError('Invalid form.') # grabbing list of configured iptable rules for the specified chain. output = run(f'sudo iptables -nL {fw_rule.zone} --line-number', shell=True, capture_output=True).stdout.splitlines()[1:] rule_count = len(output) fw_rule.position = convert_int(fw_rule.position) if (not rule_count and fw_rule.position != 1): raise ValidationError('First firewall rule must have position 1.') if (not 0 < fw_rule.position <= rule_count + 1): raise ValidationError( f'Position outside of valid range. (1-{rule_count+1})') if (fw_rule.protocol not in ['any', 'tcp', 'udp', 'icmp']): raise ValidationError('Network protocol is not valid.') if (fw_rule.protocol in ['any', 'icmp'] and fw_rule.dst_port): raise ValidationError('Only TCP/UDP use destination port field.')
def syslog_dropdown(syslog_time): syslog_time = convert_int(syslog_time) if (syslog_time): raise ValidationError('Dropdown values must be an integer.') if (syslog_time not in [5, 10, 60]): raise ValidationError('Dropdown values can only be 5, 10, or 60.')
def set_dhcp_reservation(dhcp_settings, action): with ConfigurationManager('dhcp_server') as dnx: dhcp_server_settings = dnx.load_configuration() leases = dhcp_server_settings['leases'] reservations = dhcp_server_settings['reservations'] reserved_ips = set( [info['ip_address'] for info in reservations.values()]) if (action is CFG.ADD): # preventing reservations being created for ips with an active dhcp lease if (dhcp_settings['ip'] in leases): raise ValidationError( f'There is an active lease with {dhcp_settings["ip"]}. Clear the lease and try again.' ) # ensuring mac address and ip address are unique if (dhcp_settings['mac'] in reservations or dhcp_settings['ip'] in reserved_ips): raise ValidationError( f'{dhcp_settings["ip"]} is already reserved.') reservations.update({ dhcp_settings['mac']: { 'zone': dhcp_settings['zone'], 'ip_address': dhcp_settings['ip'], 'description': dhcp_settings['description'] } }) elif (action is CFG.DEL): reservations.pop(dhcp_settings['mac'], None) dnx.write_configuration(dhcp_server_settings)
def log_settings(log_settings): if (log_settings['length'] not in [30, 45, 60, 90]): raise ValidationError('Invalid log settings.') try: LOG(log_settings['level']) except ValueError: raise ValidationError('Invalid log settings.')
def default_gateway(ip_addr): try: ip_addr = IPv4Address(ip_addr) except: raise ValidationError('Default gateway is not valid.') if (ip_addr.is_loopback): raise ValidationError('Default gateway cannot be 127.0.0.1/loopback.')
def dns_record_remove(dns_record_name): dns_server = load_configuration('dns_server') if (dns_record_name == 'dnx.firewall'): raise ValidationError('Cannot remove dnxfirewall dns record.') if (dns_record_name not in dns_server['records']): raise ValidationError(INVALID_FORM)
def _ip_address(ip_addr): try: ip_addr = IPv4Address(ip_addr) except: raise ValidationError('IP address is not valid.') if (ip_addr.is_loopback): raise ValidationError( '127.0.0.0/24 is reserved ip space and cannot be used.')
def domain_category_keywords(categories): dns_proxy = load_configuration('dns_proxy') domain_cats = dns_proxy['categories']['default'] for cat in categories: if (cat not in domain_cats): raise ValidationError(INVALID_FORM) if (not domain_cats[cat]['enabled']): raise ValidationError(INVALID_FORM)
def main_services(services_form): valid_services = [ 'dnx-dns-proxy', 'dnx-fw-proxy', 'dnx-dhcp-server', 'dnx-updates' ] service = services_form['service'] ruleset = services_form['ruleset'] if (service not in valid_services): raise ValidationError(INVALID_FORM) if (service in ['dnx-dns-proxy', 'dnx-ip-proxy'] and ruleset is None): raise ValidationError(INVALID_FORM)
def portscan_settings(portscan_settings): ips = load_configuration('ips') current_prevention = ips['port_scan']['enabled'] for item in portscan_settings: if (item not in ['enabled', 'reject']): raise ValidationError(INVALID_FORM) if ('reject' in portscan_settings and 'drop' not in portscan_settings and not current_prevention): raise ValidationError( 'Prevention must be enabled to configure portscan reject.')
def dns_over_tls(dns_tls_settings): dns_server = load_configuration('dns_server') current_tls = dns_server['tls']['enabled'] for item in dns_tls_settings['enabled']: if (item not in ['dns_over_tls', 'udp_fallback']): raise ValidationError(INVALID_FORM) # NOTE: current_tls shouldnt matter since tls will be in form if enabled regardless if (not current_tls and 'udp_fallback' in dns_tls_settings['enabled'] and 'dns_over_tls' not in dns_tls_settings['enabled']): raise ValidationError( 'DNS over TLS must be enabled to configure UDP fallback.')
def geolocation(region, rtype='country'): region['cfg_dir'] = convert_int(region['cfg_dir']) if (region['cfg_dir'] not in range(4)): raise ValidationError(INVALID_FORM) if (rtype == 'country'): valid_regions = load_configuration('ip_proxy')['geolocation'] elif (rtype == 'continent'): valid_regions = load_configuration('geolocation', filepath='dnx_webui/data') if region[rtype] not in valid_regions: raise ValidationError(INVALID_FORM)
def proto_port(port_str): try: proto, port = port_str.split('/') except: raise ValidationError( 'Invalid protocol/port definition. ex tcp/80 or udp/500-550') proto_int = _proto_map.get(proto, None) if (proto_int is None): raise ValidationError('Invalid protocol. Use [any, tcp, udp, icmp].') # ensuring icmp definitions conform to required format. if (proto_int == PROTO.ICMP and convert_int(port) != 0): raise ValidationError('ICMP does not support ports. Use icmp/0.') # splitting str after the "/" on "-" which is port range operator. this will make range or singular definition # handling the same. ports = [convert_int(p) for p in port.split('-', 1)] if (len(ports) == 2): if (ports[0] > ports[1]): raise ValidationError( 'Invalid port range. The start value must be less than the end. ex. 9001-9002' ) error = f'TCP/UDP port range must be between within range 1-65535 or 0 for any. ex tcp/500-550, udp/0' else: # this puts single port in range syntax ports.append(ports[0]) error = f'TCP/UDP port must be between 1-65535. ex udp/9001' # converting 0 port values to cover full range (0 is an alias for any). ICMP will not be converted to ensure # compatibility between icmp service definition vs any service. Any protocol will not be converted for same reason. if (proto_int not in [PROTO.ICMP, PROTO.ANY]): ports[0] = ports[0] if ports[0] != 0 else 1 # expanding the range out for any. this does not cause issues with icmp since it does not use ports so the second # value in a port range is is N/A for icmp, but in this case just letting it do what the others do. ports[1] = ports[1] if ports[1] != 0 else 65535 for port in ports: # port 0 is used by icmp. if 0 is used outside of icmp it gets converted to a range. if (port not in range(65536)): raise ValidationError(error) return proto_int, ports
def time_offset(offset_settings): dir_offset = offset_settings['direction'] if (dir_offset not in [' ', '-', '+']): raise ValidationError('Invalid time offset sign.') time_offset = offset_settings['time'] if (time_offset not in range(0, 15)): raise ValidationError('Invalid time offset value.') if (dir_offset == ' ' and time_offset != 0): raise ValidationError( 'Direction cannot be empty if amount is not zero.') elif (dir_offset == '-' and time_offset in [13, 14]): raise ValidationError('Invalid timezone/ time offset.')
def ip_proxy_settings(ip_hosts_settings, *, ruleset='reputation'): ip_proxy = load_configuration('ip_proxy') valid_categories = ip_proxy[ruleset] for category in ip_hosts_settings: try: category, direction = category[:-2], category[-1] except: raise ValidationError(INVALID_FORM) if (category not in valid_categories): raise ValidationError(INVALID_FORM) direction = convert_int(direction) if (direction not in range(4)): raise ValidationError(INVALID_FORM)
def dhcp_general_settings(server_settings): if (server_settings['interface'] not in ['lan', 'dmz']): raise ValidationError('Invalid interface referenced.') lease_range = server_settings['lease_range'] # clamping range into lan/dmz class C's. this will have to change later if more control over interface # configurations is implemented. for field in lease_range.values(): if (field not in range(2, 255)): raise ValidationError('DHCP ranges must be between 2 and 254.') if (lease_range['start'] >= lease_range['end']): raise ValidationError( 'DHCP pool start value must be less than the end value.')
def password(password): # calculating the length if (len(password) < 8): raise ValidationError( 'Password does not meet length requirement of 8 characters.') criteria = ( re.search(r'\d', password), re.search(r'[A-Z]', password), # searching for digits & uppercase re.search(r'[a-z]', password), re.search(r'\W', password) # searching for lowercase & symbols ) if not all(criteria): raise ValidationError( 'Password does not meet complexity requirements.')
def time_restriction(tr_settings): tr_hour = convert_int(tr_settings['hour']) tr_min = convert_int(tr_settings['minutes']) if (tr_hour not in range(1, 13) or tr_min not in [00, 15, 30, 45]): raise ValidationError('Restriction settings are not valid.') tr_hour_len = convert_int(tr_settings['length_hour']) tr_min_len = convert_int(tr_settings['length_minutes']) if (tr_hour_len not in range(1, 13) and tr_min_len not in [00, 15, 30, 45]): raise ValidationError('Restriction settings are not valid.') if (tr_settings['suffix'] not in ['AM', 'PM']): raise ValidationError('Restriction settings are not valid.')
def _remove_configuration(name): try: os.remove(f'{HOME_DIR}/dnx_system/config_backups/{name}.tar') except FileNotFoundError: raise ValidationError( f'{name} is not a valid file. reload page to see current backups.' )
def _restore_configuration(name): # restoring system default consists of deleting all usr config files if (name == 'system_default'): usr_cfg_file_path = f'{HOME_DIR}/dnx_system/data/usr' for file in os.listdir(usr_cfg_file_path): # NOTE: IMPORTANT. ensuring database does not get removed if (file.endswith('.sqlite3') or file == 'temp'): continue # exception to protect process just in case it is being removed by another user at the same time. try: os.remove(f'{usr_cfg_file_path}/{file}') except FileNotFoundError: pass Log.simple_write(LOG_NAME, 'notice', f'configuration restored to system defaults') else: # try: with tarfile.open( f'{HOME_DIR}/dnx_system/config_backups/{name}.tar', 'r') as tar: tar.extractall(path=f'{HOME_DIR}/dnx_system/data/usr/') except: raise ValidationError( 'Error while loading configuration. has the file been removed?' ) Log.simple_write(LOG_NAME, 'notice', f'configuration restored from file [{name}]')
def domain_categories(categories, ruleset): if (ruleset == 'default' and not all(['malicious' in categories, 'cryptominer' in categories])): raise ValidationError( 'Malicious and cryptominer categories cannot be disabled.') dns_proxy = load_configuration('dns_proxy') if (ruleset in ['default', 'user_defined']): cat_list = dns_proxy['categories'][ruleset] elif (ruleset in ['tlds']): cat_list = dns_proxy['tlds'] for category in categories: if category not in cat_list: raise ValidationError(INVALID_FORM)
def standard(user_input, *, override=[]): for char in user_input: if (not char.isalnum() and char not in override): override = ', '.join(override) # TODO: F**K ENGLISH. MAKE THIS MAKE SENSE PLEASE GOD. F**K. raise ValidationError( f'Standard fields can only contain alpha numeric {override}.')
def ip_network(ip_netw): '''take ip network string, validates, then returns ip network string. the return string will always be the network id of the subnet.''' try: ip_netw = IPv4Network(ip_netw) except: raise ValidationError('IP network is not valid.') return int(ip_netw.network_address), ip_netw.prefixlen
def add_ip_whitelist(whitelist_settings): # handling alphanum check. will raise exception if invalid. standard(whitelist_settings['user']) if (whitelist_settings['type'] not in ['global', 'tor']): raise ValidationError(INVALID_FORM) # if ip is valid this will return, otherwise a ValidationError will be raised. _ip_address(whitelist_settings['user'])
def del_firewall_rule(fw_rule): output = run(f'sudo iptables -nL {fw_rule.zone} --line-number', shell=True, capture_output=True).stdout.splitlines() rule_count = len(output) + 2 if (convert_int(fw_rule.position) not in range(1, rule_count)): raise ValidationError( 'Selected rule is not valid and cannot be removed.')
def add_snat_rule(nat_rule): # ensuring all necessary fields are present in the namespace before continuing. valid_fields = [ 'src_zone', 'orig_src_ip', 'new_src_ip', ] if not all([hasattr(nat_rule, x) for x in valid_fields]): raise ValidationError('Invalid form.')
def remove_dhcp_lease(ip_addr): with ConfigurationManager('dhcp_server') as dnx: dhcp_leases = dnx.load_configuration() leases = dhcp_leases['leases'] if not leases.pop(ip_addr, None): raise ValidationError(INVALID_FORM) dnx.write_configuration(dhcp_leases)
def ip_address(ip_addr=None, *, ip_iter=None): ip_iter = [] if not ip_iter else ip_iter if (not isinstance(ip_iter, list)): return ValidationError('Data format must be a list.') if (ip_addr): ip_iter.append(ip_addr) for ip in ip_iter: _ip_address(ip)
def update_custom_category(category, *, action): with ConfigurationManager('dns_proxy') as dnx: custom_category_lists = dnx.load_configuration() ud_cats = custom_category_lists['categories']['user_defined'] if (action is CFG.DEL and category != 'enabled'): ud_cats.pop(category, None) elif (action is CFG.ADD): if (len(ud_cats) >= 6): raise ValidationError( 'Only support for maximum of 6 custom categories.') elif (category in ud_cats): raise ValidationError('Custom category already exists.') ud_cats[category] = {'enabled': False} dnx.write_configuration(custom_category_lists)
def management_access(fields): SERVICE_TO_PORT = { 'webui': (80, 443), 'cli': (0, ), 'ssh': (22, ), 'ping': 1 } if (fields.zone not in ['lan', 'dmz'] or fields.service not in ['webui', 'cli', 'ssh', 'ping']): raise ValidationError(INVALID_FORM) # convert_int will return -1 if issues with form data and ValueError will cover # invalid CFG action key/vals try: action = CFG(convert_int(fields.action)) except ValueError: raise ValidationError(INVALID_FORM) fields.action = action fields.service_ports = SERVICE_TO_PORT[fields.service]