From 580e4ed66d036d87ae8330e0aba83e39e2742901 Mon Sep 17 00:00:00 2001 From: alf-cactus Date: Thu, 25 Sep 2025 15:37:40 +0200 Subject: [PATCH 01/27] hitcount forti --- .../importer/fortiadom5ff/fmgr_getter.py | 1 - .../files/importer/fortiadom5ff/fmgr_rule.py | 113 +++--------------- .../files/importer/fortiadom5ff/fwcommon.py | 5 +- 3 files changed, 19 insertions(+), 100 deletions(-) diff --git a/roles/importer/files/importer/fortiadom5ff/fmgr_getter.py b/roles/importer/files/importer/fortiadom5ff/fmgr_getter.py index 8ad342dccd..791fa93207 100644 --- a/roles/importer/files/importer/fortiadom5ff/fmgr_getter.py +++ b/roles/importer/files/importer/fortiadom5ff/fmgr_getter.py @@ -98,7 +98,6 @@ def update_config_with_fortinet_api_call(config_json, sid, api_base_url, api_pat # adding options if len(options)>0: payload['params'][0].update({'option': options}) - # payload['params'][0].update({'filter': options}) result = fortinet_api_call(sid, api_base_url, api_path, payload=payload, method=method) full_result.extend(result) diff --git a/roles/importer/files/importer/fortiadom5ff/fmgr_rule.py b/roles/importer/files/importer/fortiadom5ff/fmgr_rule.py index 5a1c0fc395..12442494bc 100644 --- a/roles/importer/files/importer/fortiadom5ff/fmgr_rule.py +++ b/roles/importer/files/importer/fortiadom5ff/fmgr_rule.py @@ -2,6 +2,7 @@ import jsonpickle import ipaddress import time +from time import strftime, localtime from fwo_const import list_delimiter, nat_postfix, dummy_ip from fwo_base import extend_string_list, sanitize from fmgr_service import create_svc_object @@ -15,7 +16,6 @@ #from fmgr_base import resolve_raw_objects, resolve_objects from models.rule import Rule, RuleNormalized, RuleAction, RuleTrack, RuleType from models.rulebase import Rulebase -import fwo_globals from models.import_state import ImportState @@ -30,7 +30,7 @@ def normalize_rulebases( import_state: ImportState, mgm_uid: str, - nativeConfig: dict, + native_config: dict, native_config_global: dict, normalized_config_dict: dict, normalized_config_global: dict, @@ -42,20 +42,20 @@ def normalize_rulebases( if normalized_config_global is not None: for normalized_rulebase_global in normalized_config_global.get('policies', []): fetched_rulebase_uids.append(normalized_rulebase_global.uid) - for gateway in nativeConfig['gateways']: + for gateway in native_config['gateways']: normalize_rulebases_for_each_link_destination( - gateway, mgm_uid, fetched_rulebase_uids, nativeConfig, native_config_global, + gateway, mgm_uid, fetched_rulebase_uids, native_config, native_config_global, is_global_loop_iteration, normalized_config_dict, normalized_config_global) # todo: parse nat rulebase here -def normalize_rulebases_for_each_link_destination(gateway, mgm_uid, fetched_rulebase_uids, nativeConfig, native_config_global, is_global_loop_iteration, normalized_config_dict, normalized_config_global): +def normalize_rulebases_for_each_link_destination(gateway, mgm_uid, fetched_rulebase_uids, native_config, native_config_global, is_global_loop_iteration, normalized_config_dict, normalized_config_global): logger = getFwoLogger() for rulebase_link in gateway['rulebase_links']: if rulebase_link['to_rulebase_uid'] not in fetched_rulebase_uids and rulebase_link['to_rulebase_uid'] != '': - rulebase_to_parse = find_rulebase_to_parse(nativeConfig['rulebases'], rulebase_link['to_rulebase_uid']) + rulebase_to_parse = find_rulebase_to_parse(native_config['rulebases'], rulebase_link['to_rulebase_uid']) # search in global rulebase found_rulebase_in_global = False if rulebase_to_parse == {} and not is_global_loop_iteration and native_config_global is not None: @@ -117,6 +117,8 @@ def parse_single_rule(normalized_config_dict, normalized_config_global, native_r rule_src_neg, rule_dst_neg, rule_svc_neg = rule_parse_negation_flags(native_rule) rule_installon = rule_parse_installon(native_rule, rulebase.name) + last_hit = rule_parse_last_hit(native_rule) + # Create the normalized rule rule_normalized = RuleNormalized( rule_num=rule_num, @@ -140,9 +142,9 @@ def parse_single_rule(normalized_config_dict, normalized_config_global, native_r rule_custom_fields=str(native_rule.get('meta fields', {})), rule_implied=False, rule_type=RuleType.ACCESS, - last_change_admin=None, #TODO: native_rule.get('_last-modified-by'), - see #3589 + last_change_admin=native_rule.get('_last-modified-by', ''), parent_rule_uid=None, - last_hit=None, # TODO: get last hit + last_hit=last_hit, rule_comment=native_rule.get('comments'), rule_src_zone=rule_src_zone, rule_dst_zone=rule_dst_zone, @@ -251,8 +253,13 @@ def rule_parse_installon(native_rule, rulebase_name): rule_installon = rulebase_name return rule_installon +def rule_parse_last_hit(native_rule): + last_hit = native_rule.get('_last_hit', None) + if last_hit != None: + last_hit = strftime("%Y-%m-%d %H:%M:%S", localtime(last_hit)) + return last_hit -def getAccessPolicy(sid, fm_api_url, native_config_domain, adom_device_vdom_policy_package_structure, adom_name, mgm_details_device, device_config, limit): +def get_access_policy(sid, fm_api_url, native_config_domain, adom_device_vdom_policy_package_structure, adom_name, mgm_details_device, device_config, limit): consolidated = '' # '/consolidated' logger = getFwoLogger() @@ -386,93 +393,7 @@ def getNatPolicy(sid, fm_api_url, nativeConfig, adom_name, device, limit): nativeConfig['rules_adom_nat'], sid, fm_api_url, "/pm/config/" + scope + "/pkg/" + pkg + '/' + nat_type, device['local_rulebase_name'], limit=limit) -# delete_v: ab hier kann sehr viel weg, ich lasses vorerst zB für die hitcounter -def normalize_rule(rule_orig, rules, native_config, rule_table, localPkgName, rule_number, src_ref_all, dst_ref_all, normalized_config_dict): - rule: dict = {'rule_src': '', 'rule_dst': '', 'rule_svc': ''} - xlate_rule = None - # rule.update({ 'control_id': import_id}) - rule.update({ 'rulebase_name': localPkgName}) # the rulebase_name will be set to the pkg_name as there is no rulebase_name in FortiMangaer - rule.update({ 'rule_ruleid': rule_orig['policyid']}) - rule.update({ 'rule_uid': rule_orig['uuid']}) - rule.update({ 'rule_num': rule_number}) - rule.update({ 'rule_name': rule_orig.get('name', None)}) - - # parse_target_gateways(rule_orig, rule, localPkgName) - - rule.update({ 'rule_implied': False }) - rule.update({ 'rule_time': None }) - rule.update({ 'rule_type': 'access' }) - rule.update({ 'parent_rule_id': None }) - - rule.update({ 'rule_comment': rule_orig.get('comments', None)}) - if rule_orig['action']==0: - rule.update({ 'rule_action': 'Drop' }) - else: - rule.update({ 'rule_action': 'Accept' }) - if 'status' in rule_orig and (rule_orig['status']=='enable' or rule_orig['status']==1): - rule.update({ 'rule_disabled': False }) - else: - rule.update({ 'rule_disabled': True }) - if rule_orig['logtraffic'] == 'disable': - rule.update({ 'rule_track': 'None'}) - else: - rule.update({ 'rule_track': 'Log'}) - - rule['rule_src'] = extend_string_list(rule['rule_src'], rule_orig, 'srcaddr', list_delimiter) - rule['rule_dst'] = extend_string_list(rule['rule_dst'], rule_orig, 'dstaddr', list_delimiter) - rule['rule_svc'] = extend_string_list(rule['rule_svc'], rule_orig, 'service', list_delimiter) - rule['rule_src'] = extend_string_list(rule['rule_src'], rule_orig, 'srcaddr6', list_delimiter) - rule['rule_dst'] = extend_string_list(rule['rule_dst'], rule_orig, 'dstaddr6', list_delimiter) - rule['rule_src'] = extend_string_list(rule['rule_src'], rule_orig, 'internet-service-src-name', list_delimiter) - - if len(rule_orig['srcintf'])>0: - src_obj_zone = add_zone_if_missing (normalized_config_dict, rule_orig['srcintf'][0]) - rule.update({ 'rule_from_zone': src_obj_zone }) # todo: currently only using the first zone - if len(rule_orig['dstintf'])>0: - dst_obj_zone = add_zone_if_missing (normalized_config_dict, rule_orig['dstintf'][0]) - rule.update({ 'rule_to_zone': dst_obj_zone }) # todo: currently only using the first zone - - if 'srcaddr-negate' in rule_orig: - rule.update({ 'rule_src_neg': rule_orig['srcaddr-negate']=='disable'}) - elif 'internet-service-src-negate' in rule_orig: - rule.update({ 'rule_src_neg': rule_orig['internet-service-src-negate']=='disable'}) - if 'dstaddr-negate' in rule_orig: - rule.update({ 'rule_dst_neg': rule_orig['dstaddr-negate']=='disable'}) - if 'service-negate' in rule_orig: - rule.update({ 'rule_svc_neg': rule_orig['service-negate']=='disable'}) - - rule.update({ 'rule_src_refs': resolve_raw_objects(rule['rule_src'], list_delimiter, native_config, 'name', 'uuid', \ - rule_type=rule_table, jwt=None, import_id=None, rule_uid=rule_orig['uuid'], object_type=NETWORK_OBJECT, mgm_id=mgm_details['id']) }) - rule.update({ 'rule_dst_refs': resolve_raw_objects(rule['rule_dst'], list_delimiter, native_config, 'name', 'uuid', \ - rule_type=rule_table, jwt=None, import_id=None, rule_uid=rule_orig['uuid'], object_type=NETWORK_OBJECT, mgm_id=mgm_details['id']) }) - rule.update({ 'rule_svc_refs': rule['rule_svc'] }) # services do not have uids, so using name instead - add_users_to_rule(rule_orig, rule) - - # new in v8.0.3: - rule.update({ 'rule_custom_fields': rule_orig.get('meta fields', None) }) - rule.update({ 'last_change_admin': rule_orig.get('_last-modified-by',None) }) - - update_hit_counters(native_config, rule_table, rule_orig, rule, localPkgName, rule_access_scope_v4, rule_access_scope_v6) - - xlate_rule = handle_combined_nat_rule(rule, rule_orig, normalized_config_dict, nat_rule_number, import_id, localPkgName, dev_id) - rules.append(rule) - if xlate_rule is not None: - rules.append(xlate_rule) - rule_number += 1 # nat rules have their own numbering - - -def update_hit_counters(native_config, rule_table, rule_orig, rule, localPkgName, rule_access_scope_v4, rule_access_scope_v6): - if rule_table in rule_access_scope_v4 and len(native_config['rules_hitcount'][localPkgName])>0: - for hitcount_config in native_config['rules_hitcount'][localPkgName][0]['firewall policy']: - if rule_orig['policyid'] == hitcount_config['last_hit']: - rule.update({ 'last_hit': time.strftime("%Y-%m-%dT%H:%M:%S%z", time.localtime(hitcount_config['policyid']))}) - elif rule_table in rule_access_scope_v6 and len(native_config['rules_hitcount'][localPkgName])>0: - for hitcount_config in native_config['rules_hitcount'][localPkgName][0]['firewall policy6']: - if rule_orig['policyid'] == hitcount_config['last_hit']: - rule.update({ 'last_hit': time.strftime("%Y-%m-%dT%H:%M:%S%z", time.localtime(hitcount_config['policyid']))}) - else: - rule.update({ 'last_hit': None}) - +# delete_v: ab hier kann sehr viel weg, ich lasses vorerst zB für die nat # pure nat rules def normalize_nat_rules(full_config, config2import, import_id, jwt=None): nat_rules = [] diff --git a/roles/importer/files/importer/fortiadom5ff/fwcommon.py b/roles/importer/files/importer/fortiadom5ff/fwcommon.py index de7061c326..d18df66999 100644 --- a/roles/importer/files/importer/fortiadom5ff/fwcommon.py +++ b/roles/importer/files/importer/fortiadom5ff/fwcommon.py @@ -18,7 +18,7 @@ from model_controllers.management_controller import ManagementController from fmgr_network import normalize_network_objects from fmgr_service import normalize_service_objects -from fmgr_rule import normalize_rulebases, getAccessPolicy +from fmgr_rule import normalize_rulebases, get_access_policy from fmgr_consts import nw_obj_types, svc_obj_types, user_obj_types from fwo_base import ConfigAction from models.fwconfig_normalized import FwConfigNormalized @@ -71,7 +71,7 @@ def get_config(config_in: FwConfigManagerListController, importState: ImportStat for mgm_details_device in adom.Devices: device_config = initialize_device_config(mgm_details_device) native_config_adom['gateways'].append(device_config) - getAccessPolicy( + get_access_policy( sid, fm_api_url, native_config_adom, adom_device_vdom_policy_package_structure, adom_name, mgm_details_device, device_config, limit) # delete_v: nat später #fmgr_rule.getNatPolicy( @@ -100,7 +100,6 @@ def initialize_native_config_domain(mgm_details : ManagementController): 'objects': [], 'rulebases': [], 'nat_rulebases': [], - 'rules_hitcount': [], 'gateways': []} def get_arbitrary_vdom(adom_device_vdom_structure): From e4caddbb153030e6f0531b57e6db7cd7bd7e95bf Mon Sep 17 00:00:00 2001 From: alf-cactus Date: Thu, 25 Sep 2025 15:39:21 +0200 Subject: [PATCH 02/27] forti hitcount --- .../files/importer/fortiadom5ff/fmgr_rule.py | 34 ------------------- 1 file changed, 34 deletions(-) diff --git a/roles/importer/files/importer/fortiadom5ff/fmgr_rule.py b/roles/importer/files/importer/fortiadom5ff/fmgr_rule.py index 12442494bc..ccfd3184bc 100644 --- a/roles/importer/files/importer/fortiadom5ff/fmgr_rule.py +++ b/roles/importer/files/importer/fortiadom5ff/fmgr_rule.py @@ -297,40 +297,6 @@ def get_access_policy(sid, fm_api_url, native_config_domain, adom_device_vdom_po # fmgr_getter.update_config_with_fortinet_api_call( # nativeConfig['rules_global_footer_v6'], sid, fm_api_url, "/pm/config/global/pkg/" + global_pkg_name + "/global/footer" + consolidated + "/policy6", local_pkg_name, limit=limit) - ### now dealing with hitcounts - - # get hitcount task number - hitcount_payload = { - "params": [ - { - "data": { - "adom": adom_name, - "pkg": local_pkg_name - } - } - ] - } - hitcount_task = fmgr_getter.fortinet_api_call( - sid, fm_api_url, "/sys/hitcount", payload=hitcount_payload, method="get") - time.sleep(2) - - if len(hitcount_task) == 0 or 'task' not in hitcount_task[0]: - logger.warning(f"did not get hitcount task for adom {adom_name} and package {local_pkg_name} - skipping hitcount") - return - - # execute hitcount task - hitcount_payload = { - "params": [ - { - "data": { - "taskid": hitcount_task[0]['task'] - } - } - ] - } - fmgr_getter.update_config_with_fortinet_api_call( - native_config_domain['rules_hitcount'], sid, fm_api_url, "/sys/task/result", local_pkg_name, payload=hitcount_payload, limit=limit) - def find_local_pkg(adom_device_vdom_policy_package_structure, adom_name, mgm_details_device): for device in adom_device_vdom_policy_package_structure[adom_name]: for vdom in adom_device_vdom_policy_package_structure[adom_name][device]: From a61d7d6025cd90519bf21bd86bd2dc6440a1fd28 Mon Sep 17 00:00:00 2001 From: alf-cactus Date: Tue, 30 Sep 2025 12:02:24 +0200 Subject: [PATCH 03/27] forti global import --- .../importer/fortiadom5ff/fmgr_getter.py | 9 -- .../files/importer/fortiadom5ff/fmgr_rule.py | 124 +++++++++++------- .../files/importer/fortiadom5ff/fwcommon.py | 59 ++++++--- 3 files changed, 116 insertions(+), 76 deletions(-) diff --git a/roles/importer/files/importer/fortiadom5ff/fmgr_getter.py b/roles/importer/files/importer/fortiadom5ff/fmgr_getter.py index 791fa93207..8adc75e898 100644 --- a/roles/importer/files/importer/fortiadom5ff/fmgr_getter.py +++ b/roles/importer/files/importer/fortiadom5ff/fmgr_getter.py @@ -158,12 +158,3 @@ def parse_device_and_vdom(fmgr_device, mgm_details_device, device_vdom_dict, fou else: device_vdom_dict.update({fmgr_device['name']: {fmgr_vdom['name']: ''}}) return found_fmgr_device - -def get_policy_packages_from_manager(sid, fm_api_url, adom=''): - if adom == '': - url = '/pm/pkg/global' - else: - url = '/pm/pkg/adom/' + adom - policy_packages_result = fortinet_api_call(sid, fm_api_url, url) - - return policy_packages_result diff --git a/roles/importer/files/importer/fortiadom5ff/fmgr_rule.py b/roles/importer/files/importer/fortiadom5ff/fmgr_rule.py index ccfd3184bc..8ffe610128 100644 --- a/roles/importer/files/importer/fortiadom5ff/fmgr_rule.py +++ b/roles/importer/files/importer/fortiadom5ff/fmgr_rule.py @@ -259,65 +259,84 @@ def rule_parse_last_hit(native_rule): last_hit = strftime("%Y-%m-%d %H:%M:%S", localtime(last_hit)) return last_hit -def get_access_policy(sid, fm_api_url, native_config_domain, adom_device_vdom_policy_package_structure, adom_name, mgm_details_device, device_config, limit): - consolidated = '' # '/consolidated' - logger = getFwoLogger() +def get_access_policy(sid, fm_api_url, native_config_adom, native_config_global, adom_device_vdom_policy_package_structure, adom_name, mgm_details_device, device_config, limit): previous_rulebase = '' - local_pkg_name = find_local_pkg(adom_device_vdom_policy_package_structure, adom_name, mgm_details_device) - # delete_v: hier global_pkg_name später - #global_pkg_name = device['global_rulebase_name'] + link_list = [] + local_pkg_name, global_pkg_name = find_packages(adom_device_vdom_policy_package_structure, adom_name, mgm_details_device) options = ['extra info', 'scope member', 'get meta'] - # pkg_name = device['package_name'] pkg_name is not used at all - - # delete_v: hier initial link wenn global header existiert - # get global header rulebase: - # if device['global_rulebase_name'] is None or device['global_rulebase_name'] == '': - # logger.debug('no global rulebase name defined in fortimanager, ADOM=' + adom_name + ', local_package=' + local_pkg_name) - # else: - # fmgr_getter.update_config_with_fortinet_api_call( - # nativeConfig['rules_global_header_v4'], sid, fm_api_url, "/pm/config/global/pkg/" + global_pkg_name + "/global/header" + consolidated + "/policy", local_pkg_name, limit=limit) - # fmgr_getter.update_config_with_fortinet_api_call( - # nativeConfig['rules_global_header_v6'], sid, fm_api_url, "/pm/config/global/pkg/" + global_pkg_name + "/global/header" + consolidated + "/policy6", local_pkg_name, limit=limit) + + previous_rulebase = get_and_link_global_rulebase( + 'header', previous_rulebase, global_pkg_name, native_config_global, sid, fm_api_url, options, limit, link_list) - is_global = False - # get local rulebase - fmgr_getter.update_config_with_fortinet_api_call( - native_config_domain['rulebases'], sid, fm_api_url, "/pm/config/adom/" + adom_name + "/pkg/" + local_pkg_name + "/firewall" + consolidated + "/policy", 'rules_adom_v4_' + local_pkg_name, options=options, limit=limit) - fmgr_getter.update_config_with_fortinet_api_call( - native_config_domain['rulebases'], sid, fm_api_url, "/pm/config/adom/" + adom_name + "/pkg/" + local_pkg_name + "/firewall" + consolidated + "/policy6", 'rules_adom_v6_' + local_pkg_name, limit=limit) - # delete_v: hier initial link immer lokal, erweitern wenn wir global header/footer holen - link_list, previous_rulebase = link_v4_and_v6_rulebase(native_config_domain['rulebases'], local_pkg_name, previous_rulebase, is_global) - device_config['rulebase_links'].extend(link_list) + previous_rulebase = get_and_link_local_rulebase( + 'rules_adom', previous_rulebase, adom_name, local_pkg_name, native_config_adom, sid, fm_api_url, options, limit, link_list) + + previous_rulebase = get_and_link_global_rulebase( + 'footer', previous_rulebase, global_pkg_name, native_config_global, sid, fm_api_url, options, limit, link_list) - # get global footer rulebase: - # if device['global_rulebase_name'] != None and device['global_rulebase_name'] != '': - # fmgr_getter.update_config_with_fortinet_api_call( - # nativeConfig['rules_global_footer_v4'], sid, fm_api_url, "/pm/config/global/pkg/" + global_pkg_name + "/global/footer" + consolidated + "/policy", local_pkg_name, limit=limit) - # fmgr_getter.update_config_with_fortinet_api_call( - # nativeConfig['rules_global_footer_v6'], sid, fm_api_url, "/pm/config/global/pkg/" + global_pkg_name + "/global/footer" + consolidated + "/policy6", local_pkg_name, limit=limit) + device_config['rulebase_links'].extend(link_list) -def find_local_pkg(adom_device_vdom_policy_package_structure, adom_name, mgm_details_device): +def get_and_link_global_rulebase(header_or_footer, previous_rulebase, global_pkg_name, native_config_global, sid, fm_api_url, options, limit, link_list): + rulebase_type_prefix = 'rules_global_' + header_or_footer + if global_pkg_name != '': + if not is_rulebase_already_fetched(native_config_global['rulebases'], rulebase_type_prefix + '_v4_' + global_pkg_name): + fmgr_getter.update_config_with_fortinet_api_call( + native_config_global['rulebases'], + sid, fm_api_url, + '/pm/config/global/pkg/' + global_pkg_name + '/global/' + header_or_footer + '/policy', + rulebase_type_prefix + '_v4_' + global_pkg_name, + options=options, limit=limit) + if not is_rulebase_already_fetched(native_config_global['rulebases'], rulebase_type_prefix + '_v6_' + global_pkg_name): + # delete_v: hier auch options=options? + fmgr_getter.update_config_with_fortinet_api_call( + native_config_global['rulebases'], + sid, fm_api_url, + '/pm/config/global/pkg/' + global_pkg_name + '/global/' + header_or_footer + '/policy6', + rulebase_type_prefix + '_v6_' + global_pkg_name, + limit=limit) + previous_rulebase = link_rulebase(link_list, native_config_global['rulebases'], global_pkg_name, rulebase_type_prefix, previous_rulebase, True) + return previous_rulebase + +def get_and_link_local_rulebase(rulebase_type_prefix, previous_rulebase, adom_name, local_pkg_name, native_config_adom, sid, fm_api_url, options, limit, link_list): + if not is_rulebase_already_fetched(native_config_adom['rulebases'], rulebase_type_prefix + '_v4_' + local_pkg_name): + fmgr_getter.update_config_with_fortinet_api_call( + native_config_adom['rulebases'], + sid, fm_api_url, + '/pm/config/adom/' + adom_name + '/pkg/' + local_pkg_name + '/firewall/policy', + rulebase_type_prefix + '_v4_' + local_pkg_name, + options=options, limit=limit) + if not is_rulebase_already_fetched(native_config_adom['rulebases'], rulebase_type_prefix + '_v6_' + local_pkg_name): + fmgr_getter.update_config_with_fortinet_api_call( + native_config_adom['rulebases'], + sid, fm_api_url, + '/pm/config/adom/' + adom_name + '/pkg/' + local_pkg_name + '/firewall/policy6', + rulebase_type_prefix + '_v6_' + local_pkg_name, + limit=limit) + previous_rulebase = link_rulebase(link_list, native_config_adom['rulebases'], local_pkg_name, rulebase_type_prefix, previous_rulebase, False) + +def find_packages(adom_device_vdom_policy_package_structure, adom_name, mgm_details_device): for device in adom_device_vdom_policy_package_structure[adom_name]: for vdom in adom_device_vdom_policy_package_structure[adom_name][device]: if mgm_details_device['name'] == device + '_' + vdom: - return adom_device_vdom_policy_package_structure[adom_name][device][vdom] + return adom_device_vdom_policy_package_structure[adom_name][device][vdom]['local'], adom_device_vdom_policy_package_structure[adom_name][device][vdom]['global'] raise FwoDeviceWithoutLocalPackage('Could not find local package for ' + mgm_details_device['name'] + ' in Fortimanager Config') from None -def link_v4_and_v6_rulebase(rulebases, pkg_name, previous_rulebase, is_global): - link_list = [] - has_v4_data = has_rulebase_data(rulebases, 'rules_adom_v4', pkg_name) - has_v6_data = has_rulebase_data(rulebases, 'rules_adom_v6', pkg_name) - - if has_v4_data: - link_list.append(build_link(previous_rulebase, 'rules_adom_v4' + '_' + pkg_name, is_global)) - previous_rulebase = 'rules_adom_v4' + '_' + pkg_name - if has_v6_data: - link_list.append(build_link(previous_rulebase, 'rules_adom_v6' + '_' + pkg_name, is_global)) - previous_rulebase = 'rules_adom_v6' + '_' + pkg_name +def is_rulebase_already_fetched(rulebases, type): + for rulebase in rulebases: + if rulebase['type'] == type: + return True + return False + +def link_rulebase(link_list, rulebases, pkg_name, rulebase_type_prefix, previous_rulebase, is_global): + for version in ['v4', 'v6']: + full_pkg_name = rulebase_type_prefix + '_' + version + '_' + pkg_name + has_data = has_rulebase_data(rulebases, full_pkg_name, is_global) + if has_data: + link_list.append(build_link(previous_rulebase, full_pkg_name, is_global)) + previous_rulebase = full_pkg_name - return link_list, previous_rulebase - + return previous_rulebase def build_link(previous_rulebase, full_pkg_name, is_global): if previous_rulebase == '': @@ -334,14 +353,17 @@ def build_link(previous_rulebase, full_pkg_name, is_global): 'is_section': False } -def has_rulebase_data(rulebases, type_prefix, pkg_name): +def has_rulebase_data(rulebases, full_pkg_name, is_global): + """adds name and uid to rulebase and removes empty global rulebases""" has_data = False for rulebase in rulebases: - if rulebase['type'] == type_prefix + '_' + pkg_name: - rulebase.update({'name': type_prefix + '_' + pkg_name, - 'uid': type_prefix + '_' + pkg_name}) + if rulebase['type'] == full_pkg_name: + rulebase.update({'name': full_pkg_name, + 'uid': full_pkg_name}) if len(rulebase['data']) > 0: has_data = True + elif is_global: + rulebases.remove(rulebase) return has_data def getNatPolicy(sid, fm_api_url, nativeConfig, adom_name, device, limit): diff --git a/roles/importer/files/importer/fortiadom5ff/fwcommon.py b/roles/importer/files/importer/fortiadom5ff/fwcommon.py index d18df66999..aef56c3c39 100644 --- a/roles/importer/files/importer/fortiadom5ff/fwcommon.py +++ b/roles/importer/files/importer/fortiadom5ff/fwcommon.py @@ -49,7 +49,6 @@ def get_config(config_in: FwConfigManagerListController, importState: ImportStat arbitrary_vdom_for_updateable_objects = get_arbitrary_vdom(adom_device_vdom_structure) adom_device_vdom_policy_package_structure = add_policy_package_to_vdoms(adom_device_vdom_structure, sid, fm_api_url) # adom_device_vdom_policy_package_structure = {adom: {device: {vdom1: pol_pkg1}, {vdom2: pol_pkg2}}} - global_packages = get_global_packages(sid, fm_api_url, adom_device_vdom_policy_package_structure) # get global get_objects(sid, fm_api_url, native_config_global, native_config_global, '', limit, nw_obj_types, svc_obj_types, 'global', arbitrary_vdom_for_updateable_objects) @@ -72,7 +71,7 @@ def get_config(config_in: FwConfigManagerListController, importState: ImportStat device_config = initialize_device_config(mgm_details_device) native_config_adom['gateways'].append(device_config) get_access_policy( - sid, fm_api_url, native_config_adom, adom_device_vdom_policy_package_structure, adom_name, mgm_details_device, device_config, limit) + sid, fm_api_url, native_config_adom, native_config_global, adom_device_vdom_policy_package_structure, adom_name, mgm_details_device, device_config, limit) # delete_v: nat später #fmgr_rule.getNatPolicy( # sid, fm_api_url, nativeConfig, adom_name, mgm_details_device, limit) @@ -117,8 +116,6 @@ def normalize_config(import_state, native_config: dict[str,Any]) -> FwConfigMana normalized_config_global = {} # TODO: implement reading for FortiManager 5ff if 'domains' not in native_config: - logger = getFwoLogger() - logger.error("No domains found in native config. Cannot normalize config.") raise ImportInterruption("No domains found in native config. Cannot normalize config.") rewrite_native_config_obj_type_as_key(native_config) # for easier accessability of objects in normalization process @@ -223,10 +220,11 @@ def build_adom_device_vdom_structure(adom_list, sid, fm_api_url) -> dict: def add_policy_package_to_vdoms(adom_device_vdom_structure, sid, fm_api_url): adom_device_vdom_policy_package_structure = deepcopy(adom_device_vdom_structure) for adom in adom_device_vdom_policy_package_structure: - policy_packages_result = fmgr_getter.get_policy_packages_from_manager(sid, fm_api_url, adom=adom) + policy_packages_result = fmgr_getter.fortinet_api_call(sid, fm_api_url, '/pm/pkg/adom/' + adom) for policy_package in policy_packages_result: if 'scope member' in policy_package: parse_policy_package(policy_package, adom_device_vdom_policy_package_structure, adom) + add_global_policy_package_to_vdom(adom_device_vdom_policy_package_structure, sid, fm_api_url, adom) return adom_device_vdom_policy_package_structure def parse_policy_package(policy_package, adom_device_vdom_policy_package_structure, adom): @@ -235,17 +233,46 @@ def parse_policy_package(policy_package, adom_device_vdom_policy_package_structu if device == scope_member['name']: for vdom in adom_device_vdom_policy_package_structure[adom][device]: if vdom == scope_member['vdom']: - adom_device_vdom_policy_package_structure[adom][device].update({vdom: policy_package['name']}) - -def get_global_packages(sid, fm_api_url, adom_device_vdom_policy_package_structure) -> list: - global_packages = [] - global_packages_results = fmgr_getter.get_policy_packages_from_manager(sid, fm_api_url, adom='') - for policy_package in global_packages_results: - if 'scope member' in policy_package: - for adom in adom_device_vdom_policy_package_structure: - if policy_package['scope member'] == adom: - global_packages.append(policy_package['name']) - return global_packages + adom_device_vdom_policy_package_structure[adom][device].update({vdom: {'local': policy_package['name'], 'global': ''}}) + +def add_global_policy_package_to_vdom(adom_device_vdom_policy_package_structure, sid, fm_api_url, adom): + global_assignment_result = fmgr_getter.fortinet_api_call(sid, fm_api_url, '/pm/config/adom/' + adom + '/_adom/options') + for global_assignment in global_assignment_result: + if global_assignment['assign_excluded'] == 'disable' and global_assignment['specify_assign_pkg'] == 'disable': + assign_case_all(adom_device_vdom_policy_package_structure, adom, global_assignment) + elif global_assignment['assign_excluded'] == 'disable' and global_assignment['specify_assign_pkg'] == 'enable': + assign_case_include(adom_device_vdom_policy_package_structure, adom, global_assignment) + elif global_assignment['assign_excluded'] == 'enable' and global_assignment['specify_assign_pkg'] == 'enable': + assign_case_exclude(adom_device_vdom_policy_package_structure, adom, global_assignment) + else: + raise ImportInterruption('Broken global assign format.') + +def assign_case_all(adom_device_vdom_policy_package_structure, adom, global_assignment): + for device in adom_device_vdom_policy_package_structure[adom]: + for vdom in adom_device_vdom_policy_package_structure[adom][device]: + vdom['global'] = global_assignment['assign_name'] + +def assign_case_include(adom_device_vdom_policy_package_structure, adom, global_assignment): + for device in adom_device_vdom_policy_package_structure[adom]: + for vdom in adom_device_vdom_policy_package_structure[adom][device]: + match_assign_and_vdom_policy_package(global_assignment, vdom, True) + +def assign_case_exclude(adom_device_vdom_policy_package_structure, adom, global_assignment): + for device in adom_device_vdom_policy_package_structure[adom]: + for vdom in adom_device_vdom_policy_package_structure[adom][device]: + match_assign_and_vdom_policy_package(global_assignment, vdom, False) + +def match_assign_and_vdom_policy_package(global_assignment, vdom, is_include): + for package in global_assignment['pkg_list']: + if is_include: + if package['name'] == vdom['local']: + vdom['global'] = global_assignment['assign_name'] + else: + if package['name'] != vdom['local']: + vdom['global'] = global_assignment['assign_name'] + + + def initialize_device_config(mgm_details_device): device_config = {'name': mgm_details_device['name'], From 84182b5eb48e81ca0bc386d95fdb81b38bc1bb4b Mon Sep 17 00:00:00 2001 From: alf-cactus Date: Tue, 30 Sep 2025 15:34:17 +0200 Subject: [PATCH 04/27] bug fixes --- .../files/importer/fortiadom5ff/fwcommon.py | 37 ++++++++----------- 1 file changed, 16 insertions(+), 21 deletions(-) diff --git a/roles/importer/files/importer/fortiadom5ff/fwcommon.py b/roles/importer/files/importer/fortiadom5ff/fwcommon.py index aef56c3c39..2848b33567 100644 --- a/roles/importer/files/importer/fortiadom5ff/fwcommon.py +++ b/roles/importer/files/importer/fortiadom5ff/fwcommon.py @@ -112,9 +112,6 @@ def normalize_config(import_state, native_config: dict[str,Any]) -> FwConfigMana manager_list = FwConfigManagerListController() - native_config_global = {} # TODO: implement reading for FortiManager 5ff - normalized_config_global = {} # TODO: implement reading for FortiManager 5ff - if 'domains' not in native_config: raise ImportInterruption("No domains found in native config. Cannot normalize config.") @@ -123,12 +120,13 @@ def normalize_config(import_state, native_config: dict[str,Any]) -> FwConfigMana for native_conf in native_config['domains']: normalized_config_dict = deepcopy(fwo_const.emptyNormalizedFwConfigJsonDict) + if native_conf['is-super-manager']: + native_config_global = native_conf + normalized_config_global = normalized_config_dict + # delete_v: is_global_loop_iteration scheint immer False zu sein, kann dann weg normalize_single_manager_config(native_conf, native_config_global, normalized_config_dict, normalized_config_global, import_state, is_global_loop_iteration=False) - - if native_conf['is-super-manager']: - normalized_config_global = deepcopy(normalized_config_dict) normalized_config = FwConfigNormalized( action=ConfigAction.INSERT, @@ -238,11 +236,11 @@ def parse_policy_package(policy_package, adom_device_vdom_policy_package_structu def add_global_policy_package_to_vdom(adom_device_vdom_policy_package_structure, sid, fm_api_url, adom): global_assignment_result = fmgr_getter.fortinet_api_call(sid, fm_api_url, '/pm/config/adom/' + adom + '/_adom/options') for global_assignment in global_assignment_result: - if global_assignment['assign_excluded'] == 'disable' and global_assignment['specify_assign_pkg'] == 'disable': + if global_assignment['assign_excluded'] == 0 and global_assignment['specify_assign_pkg_list'] == 0: assign_case_all(adom_device_vdom_policy_package_structure, adom, global_assignment) - elif global_assignment['assign_excluded'] == 'disable' and global_assignment['specify_assign_pkg'] == 'enable': + elif global_assignment['assign_excluded'] == 0 and global_assignment['specify_assign_pkg_list'] == 1: assign_case_include(adom_device_vdom_policy_package_structure, adom, global_assignment) - elif global_assignment['assign_excluded'] == 'enable' and global_assignment['specify_assign_pkg'] == 'enable': + elif global_assignment['assign_excluded'] == 1 and global_assignment['specify_assign_pkg_list'] == 1: assign_case_exclude(adom_device_vdom_policy_package_structure, adom, global_assignment) else: raise ImportInterruption('Broken global assign format.') @@ -250,29 +248,26 @@ def add_global_policy_package_to_vdom(adom_device_vdom_policy_package_structure, def assign_case_all(adom_device_vdom_policy_package_structure, adom, global_assignment): for device in adom_device_vdom_policy_package_structure[adom]: for vdom in adom_device_vdom_policy_package_structure[adom][device]: - vdom['global'] = global_assignment['assign_name'] + adom_device_vdom_policy_package_structure[adom][device][vdom]['global'] = global_assignment['assign_name'] def assign_case_include(adom_device_vdom_policy_package_structure, adom, global_assignment): for device in adom_device_vdom_policy_package_structure[adom]: for vdom in adom_device_vdom_policy_package_structure[adom][device]: - match_assign_and_vdom_policy_package(global_assignment, vdom, True) + match_assign_and_vdom_policy_package(global_assignment, adom_device_vdom_policy_package_structure[adom][device][vdom], True) def assign_case_exclude(adom_device_vdom_policy_package_structure, adom, global_assignment): for device in adom_device_vdom_policy_package_structure[adom]: for vdom in adom_device_vdom_policy_package_structure[adom][device]: - match_assign_and_vdom_policy_package(global_assignment, vdom, False) + match_assign_and_vdom_policy_package(global_assignment, adom_device_vdom_policy_package_structure[adom][device][vdom], False) -def match_assign_and_vdom_policy_package(global_assignment, vdom, is_include): - for package in global_assignment['pkg_list']: +def match_assign_and_vdom_policy_package(global_assignment, vdom_structure, is_include): + for package in global_assignment['pkg list']: if is_include: - if package['name'] == vdom['local']: - vdom['global'] = global_assignment['assign_name'] + if package['name'] == vdom_structure['local']: + vdom_structure['global'] = global_assignment['assign_name'] else: - if package['name'] != vdom['local']: - vdom['global'] = global_assignment['assign_name'] - - - + if package['name'] != vdom_structure['local']: + vdom_structure['global'] = global_assignment['assign_name'] def initialize_device_config(mgm_details_device): device_config = {'name': mgm_details_device['name'], From 12b7b13cc9adbe89331c63a4b0bf0004434d7835 Mon Sep 17 00:00:00 2001 From: alf-cactus Date: Wed, 1 Oct 2025 06:51:42 +0200 Subject: [PATCH 05/27] CONSTRAINT object_obj_ip_not_null --- .../files/sql/creation/fworch-create-constraints.sql | 4 ++-- roles/database/files/upgrade/9.0.sql | 6 ++++++ 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/roles/database/files/sql/creation/fworch-create-constraints.sql b/roles/database/files/sql/creation/fworch-create-constraints.sql index 74fffb3ba6..e693ce3722 100755 --- a/roles/database/files/sql/creation/fworch-create-constraints.sql +++ b/roles/database/files/sql/creation/fworch-create-constraints.sql @@ -17,8 +17,8 @@ Alter Table "import_control" add Constraint "control_id_stop_time_unique" UNIQUE Alter Table "object" add Constraint "obj_altkey" UNIQUE ("mgm_id","zone_id","obj_uid","obj_create"); ALTER TABLE object ADD CONSTRAINT object_obj_ip_is_host CHECK (is_single_ip(obj_ip)); ALTER TABLE object ADD CONSTRAINT object_obj_ip_end_is_host CHECK (is_single_ip(obj_ip_end)); -ALTER TABLE object ADD CONSTRAINT object_obj_ip_not_null CHECK (obj_ip IS NOT NULL OR obj_typ_id=2); -ALTER TABLE object ADD CONSTRAINT object_obj_ip_end_not_null CHECK (obj_ip_end IS NOT NULL OR obj_typ_id=2); +ALTER TABLE "object" ADD CONSTRAINT object_obj_ip_not_null CHECK NOT (obj_ip IS NULL AND obj_typ_id IN (1, 3, 4)); +ALTER TABLE "object" ADD CONSTRAINT object_obj_ip_end_not_null CHECK NOT (obj_ip_end IS NULL AND obj_typ_id IN (1, 3, 4)); ALTER TABLE owner ADD CONSTRAINT owner_name_unique_in_tenant UNIQUE ("name","tenant_id"); ALTER TABLE owner_network ADD CONSTRAINT port_in_valid_range CHECK (port > 0 and port <= 65535); ALTER TABLE owner_network ADD CONSTRAINT owner_network_ip_is_host CHECK (is_single_ip(ip)); diff --git a/roles/database/files/upgrade/9.0.sql b/roles/database/files/upgrade/9.0.sql index fe03e6c99b..40d0802a74 100644 --- a/roles/database/files/upgrade/9.0.sql +++ b/roles/database/files/upgrade/9.0.sql @@ -994,6 +994,12 @@ ALTER TABLE "rulebase_link" ADD COLUMN IF NOT EXISTS "removed" BIGINT; -- add obj type access-role for cp import INSERT INTO stm_obj_typ (obj_typ_id,obj_typ_name) VALUES (21,'access-role') ON CONFLICT DO NOTHING; +-- change ip not Null constraint, only applicable if obj_typ_name is host, network or machines_range +ALTER TABLE "object" DROP CONSTRAINT IF EXISTS "object_obj_ip_not_null" CASCADE; +ALTER TABLE "object" DROP CONSTRAINT IF EXISTS "object_obj_ip_end_not_null" CASCADE; +ALTER TABLE "object" ADD CONSTRAINT object_obj_ip_not_null CHECK NOT (obj_ip IS NULL AND obj_typ_id IN (1, 3, 4)); +ALTER TABLE "object" ADD CONSTRAINT object_obj_ip_end_not_null CHECK NOT (obj_ip_end IS NULL AND obj_typ_id IN (1, 3, 4)); + -- remove dev_id fk and set nullable if column exists ALTER TABLE changelog_rule DROP CONSTRAINT IF EXISTS changelog_rule_dev_id_fkey; From eff34cb7c2b01ef6e3999235aba4ed528f6866fa Mon Sep 17 00:00:00 2001 From: alf-cactus Date: Wed, 1 Oct 2025 14:23:35 +0200 Subject: [PATCH 06/27] error handling in common --- roles/importer/files/importer/common.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/roles/importer/files/importer/common.py b/roles/importer/files/importer/common.py index fb607edaa6..b10b83ecdc 100644 --- a/roles/importer/files/importer/common.py +++ b/roles/importer/files/importer/common.py @@ -92,8 +92,9 @@ def import_management(mgmId=None, ssl_verification=None, debug_level_in=0, except (FwoApiWriteError, FwoImporterError) as e: importState.addError(f"FwoApiWriteError or FwoImporterError: {str(e.args)} - aborting import") rollBackExceptionHandler(importState, configImporter=config_importer, exc=e, errorText="") - except FwoImporterErrorInconsistencies: + except FwoImporterErrorInconsistencies as e: importState.delete_import() # delete whole import + importState.addError(str(e.args)) except ValueError: importState.addError("ValueError - aborting import") raise From 66e77a13d4dd433da67e81d9edf0d31063ede8a7 Mon Sep 17 00:00:00 2001 From: alf-cactus Date: Wed, 1 Oct 2025 16:39:43 +0200 Subject: [PATCH 07/27] forti links --- roles/importer/files/importer/fortiadom5ff/fmgr_rule.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/roles/importer/files/importer/fortiadom5ff/fmgr_rule.py b/roles/importer/files/importer/fortiadom5ff/fmgr_rule.py index 8ffe610128..0ac4538199 100644 --- a/roles/importer/files/importer/fortiadom5ff/fmgr_rule.py +++ b/roles/importer/files/importer/fortiadom5ff/fmgr_rule.py @@ -261,7 +261,7 @@ def rule_parse_last_hit(native_rule): def get_access_policy(sid, fm_api_url, native_config_adom, native_config_global, adom_device_vdom_policy_package_structure, adom_name, mgm_details_device, device_config, limit): - previous_rulebase = '' + previous_rulebase = None link_list = [] local_pkg_name, global_pkg_name = find_packages(adom_device_vdom_policy_package_structure, adom_name, mgm_details_device) options = ['extra info', 'scope member', 'get meta'] @@ -314,6 +314,7 @@ def get_and_link_local_rulebase(rulebase_type_prefix, previous_rulebase, adom_na rulebase_type_prefix + '_v6_' + local_pkg_name, limit=limit) previous_rulebase = link_rulebase(link_list, native_config_adom['rulebases'], local_pkg_name, rulebase_type_prefix, previous_rulebase, False) + return previous_rulebase def find_packages(adom_device_vdom_policy_package_structure, adom_name, mgm_details_device): for device in adom_device_vdom_policy_package_structure[adom_name]: @@ -339,7 +340,7 @@ def link_rulebase(link_list, rulebases, pkg_name, rulebase_type_prefix, previous return previous_rulebase def build_link(previous_rulebase, full_pkg_name, is_global): - if previous_rulebase == '': + if previous_rulebase is None: is_initial = True else: is_initial = False From 77c94a8cd86263551c1a5e36864092f4f2d8b765 Mon Sep 17 00:00:00 2001 From: alf-cactus Date: Fri, 3 Oct 2025 14:49:57 +0200 Subject: [PATCH 08/27] forti implicit deny wip --- .../files/importer/fortiadom5ff/fmgr_rule.py | 38 ++++++++++++++++++- 1 file changed, 36 insertions(+), 2 deletions(-) diff --git a/roles/importer/files/importer/fortiadom5ff/fmgr_rule.py b/roles/importer/files/importer/fortiadom5ff/fmgr_rule.py index 0ac4538199..a05e4763fb 100644 --- a/roles/importer/files/importer/fortiadom5ff/fmgr_rule.py +++ b/roles/importer/files/importer/fortiadom5ff/fmgr_rule.py @@ -68,7 +68,7 @@ def normalize_rulebases_for_each_link_destination(gateway, mgm_uid, fetched_rule continue normalized_rulebase = initialize_normalized_rulebase(rulebase_to_parse, mgm_uid) - parse_rulebase(normalized_config_dict, normalized_config_global, rulebase_to_parse, normalized_rulebase) + parse_rulebase(normalized_config_dict, normalized_config_global, rulebase_to_parse, normalized_rulebase, found_rulebase_in_global) fetched_rulebase_uids.append(rulebase_link['to_rulebase_uid']) if found_rulebase_in_global: @@ -91,11 +91,45 @@ def initialize_normalized_rulebase(rulebase_to_parse, mgm_uid): normalized_rulebase = Rulebase(uid=rulebaseUid, name=rulebaseName, mgm_uid=mgm_uid, Rules={}) return normalized_rulebase -def parse_rulebase(normalized_config_dict, normalized_config_global, rulebase_to_parse, normalized_rulebase): +def parse_rulebase(normalized_config_dict, normalized_config_global, rulebase_to_parse, normalized_rulebase, found_rulebase_in_global): rule_num = 1 for native_rule in rulebase_to_parse['data']: rule_num = parse_single_rule(normalized_config_dict, normalized_config_global, native_rule, normalized_rulebase, rule_num) + if not found_rulebase_in_global: + add_implicit_deny_rule(rule_num) + +def add_implicit_deny_rule(rule_num): + rule_normalized = RuleNormalized( + rule_num=rule_num, + rule_num_numeric=0, + rule_disabled=False, + rule_src_neg=False, + rule_src=list_delimiter.join(rule_src_list), + rule_src_refs=list_delimiter.join(rule_src_refs_list), + rule_dst_neg=False, + rule_dst=list_delimiter.join(rule_dst_list), + rule_dst_refs=list_delimiter.join(rule_dst_refs_list), + rule_svc_neg=False, + rule_svc=list_delimiter.join(rule_svc_list), + rule_svc_refs=list_delimiter.join(rule_svc_refs_list), + rule_action=RuleAction.DROP, + rule_track=RuleTrack.NONE, + rule_installon='', + rule_time='', # Time-based rules not commonly used in basic Fortinet configs + rule_name='Implicit Deny', + rule_uid='', + rule_custom_fields={}, + rule_implied=False, + rule_type=RuleType.ACCESS, + last_change_admin='', + parent_rule_uid=None, + last_hit=last_hit, + rule_comment='', + rule_src_zone=None, + rule_dst_zone=rule_dst_zone, + rule_head_text=None + ) def parse_single_rule(normalized_config_dict, normalized_config_global, native_rule, rulebase: Rulebase, rule_num): # Extract basic rule information From 6cd97a870f5574fa0840860818871d37ba0e2466 Mon Sep 17 00:00:00 2001 From: alf-cactus Date: Sun, 5 Oct 2025 15:23:53 +0200 Subject: [PATCH 09/27] forti deny rule and zones --- .../files/importer/fortiadom5ff/fmgr_rule.py | 23 +++++++---- .../files/importer/fortiadom5ff/fmgr_zone.py | 39 ++++++++++++++++++ .../files/importer/fortiadom5ff/fwcommon.py | 40 ++----------------- 3 files changed, 58 insertions(+), 44 deletions(-) diff --git a/roles/importer/files/importer/fortiadom5ff/fmgr_rule.py b/roles/importer/files/importer/fortiadom5ff/fmgr_rule.py index a05e4763fb..693e459049 100644 --- a/roles/importer/files/importer/fortiadom5ff/fmgr_rule.py +++ b/roles/importer/files/importer/fortiadom5ff/fmgr_rule.py @@ -99,7 +99,14 @@ def parse_rulebase(normalized_config_dict, normalized_config_global, rulebase_to if not found_rulebase_in_global: add_implicit_deny_rule(rule_num) -def add_implicit_deny_rule(rule_num): +def add_implicit_deny_rule(rule_num, normalized_config_dict, normalized_config_global): + + deny_rule = {'srcaddr': ['all'], 'srcaddr6': ['all'], 'dstaddr': ['all'], 'dstaddr6': ['all'], 'service': ['ALL']} + + rule_src_list, rule_src_refs_list = rule_parse_addresses(deny_rule, 'src', normalized_config_dict, normalized_config_global) + rule_dst_list, rule_dst_refs_list = rule_parse_addresses(deny_rule, 'dst', normalized_config_dict, normalized_config_global) + rule_svc_list, rule_svc_refs_list = rule_parse_service(deny_rule) + rule_normalized = RuleNormalized( rule_num=rule_num, rule_num_numeric=0, @@ -124,7 +131,7 @@ def add_implicit_deny_rule(rule_num): rule_type=RuleType.ACCESS, last_change_admin='', parent_rule_uid=None, - last_hit=last_hit, + last_hit=None, rule_comment='', rule_src_zone=None, rule_dst_zone=rule_dst_zone, @@ -217,15 +224,15 @@ def rule_parse_service(native_rule): return rule_svc_list, rule_svc_refs_list def rule_parse_zone(native_rule, normalized_config_dict): - # TODO: only using the first zone for now - rule_src_zone = None + + rule_src_zone_list = [None] if len(native_rule.get('srcintf', [])) > 0: - rule_src_zone = add_zone_if_missing(normalized_config_dict, native_rule['srcintf'][0]) + rule_src_zone_list = add_zone_if_missing(normalized_config_dict, native_rule['srcintf'][0]) - rule_dst_zone = None + rule_dst_zone_list = [None] if len(native_rule.get('dstintf', [])) > 0: - rule_dst_zone = add_zone_if_missing(normalized_config_dict, native_rule['dstintf'][0]) - return rule_src_zone, rule_dst_zone + rule_dst_zone_list = add_zone_if_missing(normalized_config_dict, native_rule['dstintf'][0]) + return rule_src_zone_list, rule_dst_zone_list def rule_parse_addresses(native_rule, target, normalized_config_dict, normalized_config_global): if target not in ['src', 'dst']: diff --git a/roles/importer/files/importer/fortiadom5ff/fmgr_zone.py b/roles/importer/files/importer/fortiadom5ff/fmgr_zone.py index 1335efcc23..9409402fdd 100644 --- a/roles/importer/files/importer/fortiadom5ff/fmgr_zone.py +++ b/roles/importer/files/importer/fortiadom5ff/fmgr_zone.py @@ -1,3 +1,42 @@ +import fmgr_getter + +def get_zones(sid, fm_api_url, native_config, adom_name, limit): + native_config.update({"zones": {}}) + + # get global zones + if adom_name == '': + fmgr_getter.update_config_with_fortinet_api_call( + native_config['zones'], sid, fm_api_url, '/pm/config/global/obj/dynamic/interface', 'interface_global', limit=limit) + else: + fmgr_getter.update_config_with_fortinet_api_call( + native_config['zones'], sid, fm_api_url, '/pm/config/adom/' + adom_name + '/obj/dynamic/interface', 'interface_' + adom_name, limit=limit) + + # # get local zones + # for device in native_config['devices']: + # local_pkg_name = device['package'] + # for adom in native_config['adoms']: + # if adom['name']==adom_name: + # if local_pkg_name not in adom['package_names']: + # logger.error('local rulebase/package ' + local_pkg_name + ' not found in management ' + adom_name) + # return 1 + # else: + # fmgr_getter.update_config_with_fortinet_api_call( + # native_config['zones'], sid, fm_api_url, "/pm/config/adom/" + adom_name + "/obj/dynamic/interface", device['id'], debug=debug_level, limit=limit) + + # native_config['zones']['zone_list'] = [] + # for device in native_config['zones']: + # for mapping in native_config['zones'][device]: + # if not isinstance(mapping, str): + # if not mapping['dynamic_mapping'] is None: + # for dyn_mapping in mapping['dynamic_mapping']: + # if 'name' in dyn_mapping and not dyn_mapping['name'] in native_config['zones']['zone_list']: + # native_config['zones']['zone_list'].append(dyn_mapping['name']) + # if 'local-intf' in dyn_mapping and not dyn_mapping['local-intf'][0] in native_config['zones']['zone_list']: + # native_config['zones']['zone_list'].append(dyn_mapping['local-intf'][0]) + # if not mapping['platform_mapping'] is None: + # for dyn_mapping in mapping['platform_mapping']: + # if 'intf-zone' in dyn_mapping and not dyn_mapping['intf-zone'] in native_config['zones']['zone_list']: + # native_config['zones']['zone_list'].append(dyn_mapping['intf-zone']) def normalize_zones(full_config, config2import, import_id): zones = [] diff --git a/roles/importer/files/importer/fortiadom5ff/fwcommon.py b/roles/importer/files/importer/fortiadom5ff/fwcommon.py index 2848b33567..686108fa19 100644 --- a/roles/importer/files/importer/fortiadom5ff/fwcommon.py +++ b/roles/importer/files/importer/fortiadom5ff/fwcommon.py @@ -21,6 +21,7 @@ from fmgr_rule import normalize_rulebases, get_access_policy from fmgr_consts import nw_obj_types, svc_obj_types, user_obj_types from fwo_base import ConfigAction +from fmgr_zone import get_zones from models.fwconfig_normalized import FwConfigNormalized @@ -48,10 +49,10 @@ def get_config(config_in: FwConfigManagerListController, importState: ImportStat # delete_v: das geht schief für unschöne adoms arbitrary_vdom_for_updateable_objects = get_arbitrary_vdom(adom_device_vdom_structure) adom_device_vdom_policy_package_structure = add_policy_package_to_vdoms(adom_device_vdom_structure, sid, fm_api_url) - # adom_device_vdom_policy_package_structure = {adom: {device: {vdom1: pol_pkg1}, {vdom2: pol_pkg2}}} # get global get_objects(sid, fm_api_url, native_config_global, native_config_global, '', limit, nw_obj_types, svc_obj_types, 'global', arbitrary_vdom_for_updateable_objects) + get_zones(sid, fm_api_url, native_config_global, '', limit) for adom in adom_list: adom_name = adom.DomainName @@ -61,7 +62,7 @@ def get_config(config_in: FwConfigManagerListController, importState: ImportStat adom_scope = 'adom/'+adom_name get_objects(sid, fm_api_url, native_config_adom, native_config_global, adom_name, limit, nw_obj_types, svc_obj_types, adom_scope, arbitrary_vdom_for_updateable_objects) # currently reading zone from objects/rules for backward compat with FortiManager 6.x - # getZones(sid, fm_api_url, full_config, adom_name, limit, debug_level) + get_zones(sid, fm_api_url, native_config_adom, adom_name, limit) # todo: bring interfaces and routing in new domain native config format #getInterfacesAndRouting( @@ -99,6 +100,7 @@ def initialize_native_config_domain(mgm_details : ManagementController): 'objects': [], 'rulebases': [], 'nat_rulebases': [], + 'zones': [], 'gateways': []} def get_arbitrary_vdom(adom_device_vdom_structure): @@ -359,37 +361,3 @@ def normalize_links(rulebase_links : list): if link['from_rule_uid'] != None: link['from_rule_uid'] = None return rulebase_links - -# def getZones(sid, fm_api_url, nativeConfig, adom_name, limit, debug_level): -# nativeConfig.update({"zones": {}}) - -# # get global zones? - -# # get local zones -# for device in nativeConfig['devices']: -# local_pkg_name = device['package'] -# for adom in nativeConfig['adoms']: -# if adom['name']==adom_name: -# if local_pkg_name not in adom['package_names']: -# logger.error('local rulebase/package ' + local_pkg_name + ' not found in management ' + adom_name) -# return 1 -# else: -# fmgr_getter.update_config_with_fortinet_api_call( -# nativeConfig['zones'], sid, fm_api_url, "/pm/config/adom/" + adom_name + "/obj/dynamic/interface", device['id'], debug=debug_level, limit=limit) - -# nativeConfig['zones']['zone_list'] = [] -# for device in nativeConfig['zones']: -# for mapping in nativeConfig['zones'][device]: -# if not isinstance(mapping, str): -# if not mapping['dynamic_mapping'] is None: -# for dyn_mapping in mapping['dynamic_mapping']: -# if 'name' in dyn_mapping and not dyn_mapping['name'] in nativeConfig['zones']['zone_list']: -# nativeConfig['zones']['zone_list'].append(dyn_mapping['name']) -# if 'local-intf' in dyn_mapping and not dyn_mapping['local-intf'][0] in nativeConfig['zones']['zone_list']: -# nativeConfig['zones']['zone_list'].append(dyn_mapping['local-intf'][0]) -# if not mapping['platform_mapping'] is None: -# for dyn_mapping in mapping['platform_mapping']: -# if 'intf-zone' in dyn_mapping and not dyn_mapping['intf-zone'] in nativeConfig['zones']['zone_list']: -# nativeConfig['zones']['zone_list'].append(dyn_mapping['intf-zone']) - - From 0bcfc3e43a630c02bc2c196a796ed5383d25cc70 Mon Sep 17 00:00:00 2001 From: alf-cactus Date: Sun, 5 Oct 2025 18:32:01 +0200 Subject: [PATCH 10/27] forti zones --- roles/importer/files/importer/fortiadom5ff/fmgr_rule.py | 5 +++-- roles/importer/files/importer/fortiadom5ff/fmgr_zone.py | 3 +-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/roles/importer/files/importer/fortiadom5ff/fmgr_rule.py b/roles/importer/files/importer/fortiadom5ff/fmgr_rule.py index 693e459049..f2efef7c0f 100644 --- a/roles/importer/files/importer/fortiadom5ff/fmgr_rule.py +++ b/roles/importer/files/importer/fortiadom5ff/fmgr_rule.py @@ -97,9 +97,9 @@ def parse_rulebase(normalized_config_dict, normalized_config_global, rulebase_to for native_rule in rulebase_to_parse['data']: rule_num = parse_single_rule(normalized_config_dict, normalized_config_global, native_rule, normalized_rulebase, rule_num) if not found_rulebase_in_global: - add_implicit_deny_rule(rule_num) + add_implicit_deny_rule(rule_num, normalized_config_dict, normalized_config_global, normalized_rulebase) -def add_implicit_deny_rule(rule_num, normalized_config_dict, normalized_config_global): +def add_implicit_deny_rule(rule_num, normalized_config_dict, normalized_config_global, rulebase: Rulebase): deny_rule = {'srcaddr': ['all'], 'srcaddr6': ['all'], 'dstaddr': ['all'], 'dstaddr6': ['all'], 'service': ['ALL']} @@ -137,6 +137,7 @@ def add_implicit_deny_rule(rule_num, normalized_config_dict, normalized_config_g rule_dst_zone=rule_dst_zone, rule_head_text=None ) + rulebase.Rules[rule_normalized.rule_uid] = rule_normalized def parse_single_rule(normalized_config_dict, normalized_config_global, native_rule, rulebase: Rulebase, rule_num): # Extract basic rule information diff --git a/roles/importer/files/importer/fortiadom5ff/fmgr_zone.py b/roles/importer/files/importer/fortiadom5ff/fmgr_zone.py index 9409402fdd..c4eb49acb4 100644 --- a/roles/importer/files/importer/fortiadom5ff/fmgr_zone.py +++ b/roles/importer/files/importer/fortiadom5ff/fmgr_zone.py @@ -1,8 +1,7 @@ import fmgr_getter def get_zones(sid, fm_api_url, native_config, adom_name, limit): - native_config.update({"zones": {}}) - + # get global zones if adom_name == '': fmgr_getter.update_config_with_fortinet_api_call( From 5ae7a2049a843e561efc314261287cd5329dad65 Mon Sep 17 00:00:00 2001 From: alf-cactus Date: Tue, 7 Oct 2025 14:41:31 +0200 Subject: [PATCH 11/27] forti nat start --- .../files/importer/fortiadom5ff/fmgr_rule.py | 58 ++++++++++++------- .../files/importer/fortiadom5ff/fmgr_zone.py | 1 - .../files/importer/fortiadom5ff/fwcommon.py | 12 ++-- 3 files changed, 44 insertions(+), 27 deletions(-) diff --git a/roles/importer/files/importer/fortiadom5ff/fmgr_rule.py b/roles/importer/files/importer/fortiadom5ff/fmgr_rule.py index f2efef7c0f..91e605d801 100644 --- a/roles/importer/files/importer/fortiadom5ff/fmgr_rule.py +++ b/roles/importer/files/importer/fortiadom5ff/fmgr_rule.py @@ -409,30 +409,44 @@ def has_rulebase_data(rulebases, full_pkg_name, is_global): rulebases.remove(rulebase) return has_data -def getNatPolicy(sid, fm_api_url, nativeConfig, adom_name, device, limit): - scope = 'global' - pkg = device['global_rulebase_name'] - if pkg is not None and pkg != '': # only read global rulebase if it exists +def get_nat_policy(sid, fm_api_url, native_config, adom_device_vdom_policy_package_structure, adom_name, mgm_details_device, limit): + local_pkg_name, global_pkg_name = find_packages(adom_device_vdom_policy_package_structure, adom_name, mgm_details_device) + if adom_name == '': + for nat_type in ['central/dnat', 'central/dnat6', 'firewall/central-snat-map']: + fmgr_getter.update_config_with_fortinet_api_call( + native_config['nat_rulebases'], sid, fm_api_url, + '/pm/config/global/pkg/' + global_pkg_name + '/' + nat_type, + nat_type + '_global_' + global_pkg_name, limit=limit) + else: for nat_type in ['central/dnat', 'central/dnat6', 'firewall/central-snat-map']: fmgr_getter.update_config_with_fortinet_api_call( - nativeConfig['rules_global_nat'], sid, fm_api_url, "/pm/config/" + scope + "/pkg/" + pkg + '/' + nat_type, device['local_rulebase_name'], limit=limit) + native_config['nat_rulebases'], sid, fm_api_url, + '/pm/config/adom/' + adom_name + '/pkg/' + local_pkg_name + '/' + nat_type, + nat_type + '_adom_' + adom_name + '_' + local_pkg_name, limit=limit) - scope = 'adom/'+adom_name - pkg = device['local_rulebase_name'] - for nat_type in ['central/dnat', 'central/dnat6', 'firewall/central-snat-map']: - fmgr_getter.update_config_with_fortinet_api_call( - nativeConfig['rules_adom_nat'], sid, fm_api_url, "/pm/config/" + scope + "/pkg/" + pkg + '/' + nat_type, device['local_rulebase_name'], limit=limit) + # scope = 'global' + # pkg = device['global_rulebase_name'] + # if pkg is not None and pkg != '': # only read global rulebase if it exists + # for nat_type in ['central/dnat', 'central/dnat6', 'firewall/central-snat-map']: + # fmgr_getter.update_config_with_fortinet_api_call( + # nativeConfig['rules_global_nat'], sid, fm_api_url, "/pm/config/" + scope + "/pkg/" + pkg + '/' + nat_type, device['local_rulebase_name'], limit=limit) + + # scope = 'adom/'+adom_name + # pkg = device['local_rulebase_name'] + # for nat_type in ['central/dnat', 'central/dnat6', 'firewall/central-snat-map']: + # fmgr_getter.update_config_with_fortinet_api_call( + # nativeConfig['rules_adom_nat'], sid, fm_api_url, "/pm/config/" + scope + "/pkg/" + pkg + '/' + nat_type, device['local_rulebase_name'], limit=limit) # delete_v: ab hier kann sehr viel weg, ich lasses vorerst zB für die nat # pure nat rules -def normalize_nat_rules(full_config, config2import, import_id, jwt=None): +def normalize_nat_rulebases(native_config, normalized_config_dict, import_id, jwt=None): nat_rules = [] rule_number = 0 for rule_table in rule_nat_scope: - for localPkgName in full_config['rules_global_nat']: - for rule_orig in full_config[rule_table][localPkgName]: + for localPkgName in native_config['rules_global_nat']: + for rule_orig in native_config[rule_table][localPkgName]: rule = {'rule_src': '', 'rule_dst': '', 'rule_svc': ''} if rule_orig['nat'] == 1: # assuming source nat rule.update({ 'control_id': import_id}) @@ -457,9 +471,9 @@ def normalize_nat_rules(full_config, config2import, import_id, jwt=None): svc_name = 'svc_' + str(rule_orig['orig-port']) # need to create a helper service object and add it to the nat rule, also needs to be added to service list - if not 'service_objects' in config2import: # is normally defined - config2import['service_objects'] = [] - config2import['service_objects'].append(create_svc_object( \ + if not 'service_objects' in normalized_config_dict: # is normally defined + normalized_config_dict['service_objects'] = [] + normalized_config_dict['service_objects'].append(create_svc_object( \ import_id=import_id, name=svc_name, proto=rule_orig['protocol'], port=rule_orig['orig-port'], comment='service created by FWO importer for NAT purposes')) rule['rule_svc'] = svc_name @@ -474,9 +488,9 @@ def normalize_nat_rules(full_config, config2import, import_id, jwt=None): rule.update({ 'rule_src_neg': False}) rule.update({ 'rule_dst_neg': False}) rule.update({ 'rule_svc_neg': False}) - rule.update({ 'rule_src_refs': resolve_raw_objects(rule['rule_src'], list_delimiter, full_config, 'name', 'uuid', rule_type=rule_table) }, \ + rule.update({ 'rule_src_refs': resolve_raw_objects(rule['rule_src'], list_delimiter, native_config, 'name', 'uuid', rule_type=rule_table) }, \ jwt=jwt, import_id=import_id, rule_uid=rule_orig['uuid'], object_type=NETWORK_OBJECT) - rule.update({ 'rule_dst_refs': resolve_raw_objects(rule['rule_dst'], list_delimiter, full_config, 'name', 'uuid', rule_type=rule_table) }, \ + rule.update({ 'rule_dst_refs': resolve_raw_objects(rule['rule_dst'], list_delimiter, native_config, 'name', 'uuid', rule_type=rule_table) }, \ jwt=jwt, import_id=import_id, rule_uid=rule_orig['uuid'], object_type=NETWORK_OBJECT) # services do not have uids, so using name instead rule.update({ 'rule_svc_refs': rule['rule_svc'] }) @@ -507,18 +521,18 @@ def normalize_nat_rules(full_config, config2import, import_id, jwt=None): svc_name = 'svc_' + str(rule_orig['nat-port']) # need to create a helper service object and add it to the nat rule, also needs to be added to service list! # fmgr_service.create_svc_object(name=svc_name, proto=rule_orig['protocol'], port=rule_orig['orig-port'], comment='service created by FWO importer for NAT purposes') - config2import['service_objects'].append(create_svc_object(import_id=import_id, name=svc_name, proto=rule_orig['protocol'], port=rule_orig['nat-port'], comment='service created by FWO importer for NAT purposes')) + normalized_config_dict['service_objects'].append(create_svc_object(import_id=import_id, name=svc_name, proto=rule_orig['protocol'], port=rule_orig['nat-port'], comment='service created by FWO importer for NAT purposes')) xlate_rule['rule_svc'] = svc_name - xlate_rule.update({ 'rule_src_refs': resolve_objects(xlate_rule['rule_src'], list_delimiter, full_config, 'name', 'uuid', rule_type=rule_table, jwt=jwt, import_id=import_id ) }) - xlate_rule.update({ 'rule_dst_refs': resolve_objects(xlate_rule['rule_dst'], list_delimiter, full_config, 'name', 'uuid', rule_type=rule_table, jwt=jwt, import_id=import_id ) }) + xlate_rule.update({ 'rule_src_refs': resolve_objects(xlate_rule['rule_src'], list_delimiter, native_config, 'name', 'uuid', rule_type=rule_table, jwt=jwt, import_id=import_id ) }) + xlate_rule.update({ 'rule_dst_refs': resolve_objects(xlate_rule['rule_dst'], list_delimiter, native_config, 'name', 'uuid', rule_type=rule_table, jwt=jwt, import_id=import_id ) }) xlate_rule.update({ 'rule_svc_refs': xlate_rule['rule_svc'] }) # services do not have uids, so using name instead xlate_rule.update({ 'rule_type': 'xlate' }) nat_rules.append(xlate_rule) rule_number += 1 - config2import['rules'].extend(nat_rules) + normalized_config_dict['rules'].extend(nat_rules) def insert_header(rules, import_id, header_text, rulebase_name, rule_uid, rule_number, src_refs, dst_refs): diff --git a/roles/importer/files/importer/fortiadom5ff/fmgr_zone.py b/roles/importer/files/importer/fortiadom5ff/fmgr_zone.py index c4eb49acb4..bef8d790ea 100644 --- a/roles/importer/files/importer/fortiadom5ff/fmgr_zone.py +++ b/roles/importer/files/importer/fortiadom5ff/fmgr_zone.py @@ -2,7 +2,6 @@ def get_zones(sid, fm_api_url, native_config, adom_name, limit): - # get global zones if adom_name == '': fmgr_getter.update_config_with_fortinet_api_call( native_config['zones'], sid, fm_api_url, '/pm/config/global/obj/dynamic/interface', 'interface_global', limit=limit) diff --git a/roles/importer/files/importer/fortiadom5ff/fwcommon.py b/roles/importer/files/importer/fortiadom5ff/fwcommon.py index 686108fa19..4b4b474f49 100644 --- a/roles/importer/files/importer/fortiadom5ff/fwcommon.py +++ b/roles/importer/files/importer/fortiadom5ff/fwcommon.py @@ -18,7 +18,7 @@ from model_controllers.management_controller import ManagementController from fmgr_network import normalize_network_objects from fmgr_service import normalize_service_objects -from fmgr_rule import normalize_rulebases, get_access_policy +from fmgr_rule import normalize_rulebases, get_access_policy, get_nat_policy, normalize_nat_rulebases from fmgr_consts import nw_obj_types, svc_obj_types, user_obj_types from fwo_base import ConfigAction from fmgr_zone import get_zones @@ -73,9 +73,8 @@ def get_config(config_in: FwConfigManagerListController, importState: ImportStat native_config_adom['gateways'].append(device_config) get_access_policy( sid, fm_api_url, native_config_adom, native_config_global, adom_device_vdom_policy_package_structure, adom_name, mgm_details_device, device_config, limit) - # delete_v: nat später - #fmgr_rule.getNatPolicy( - # sid, fm_api_url, nativeConfig, adom_name, mgm_details_device, limit) + get_nat_policy( + sid, fm_api_url, native_config_adom, adom_device_vdom_policy_package_structure, adom_name, mgm_details_device, limit) try: # logout of fortimanager API fmgr_getter.logout( @@ -198,6 +197,11 @@ def normalize_single_manager_config(native_config: dict[str, Any], native_config is_global_loop_iteration) logger.info("completed normalizing rulebases for manager: " + native_config.get('domain_name','')) + normalize_nat_rulebases(import_state, mgm_uid, native_config, native_config_global, normalized_config_dict, normalized_config_global, + is_global_loop_iteration) + normalize_nat_rulebases(native_config, normalized_config_dict, import_id, jwt=None) + logger.info("completed normalizing nat rulebases for manager: " + native_config.get('domain_name','')) + normalize_gateways(native_config, normalized_config_dict) From b0c2f2ae89d00958b79a3e6e510447e6e187ad4c Mon Sep 17 00:00:00 2001 From: alf-cactus Date: Wed, 8 Oct 2025 11:58:33 +0200 Subject: [PATCH 12/27] forti zones wip --- .../importer/fortiadom5ff/fmgr_network.py | 26 +++++---- .../files/importer/fortiadom5ff/fmgr_zone.py | 57 ++++++++----------- .../files/importer/fortiadom5ff/fwcommon.py | 8 +-- .../importer/files/importer/fwo_exceptions.py | 7 +++ 4 files changed, 50 insertions(+), 48 deletions(-) diff --git a/roles/importer/files/importer/fortiadom5ff/fmgr_network.py b/roles/importer/files/importer/fortiadom5ff/fmgr_network.py index 548aeb5be9..54d80e8b5a 100644 --- a/roles/importer/files/importer/fortiadom5ff/fmgr_network.py +++ b/roles/importer/files/importer/fortiadom5ff/fmgr_network.py @@ -6,10 +6,10 @@ from fwo_config import readConfig from model_controllers.import_state_controller import ImportStateController from copy import deepcopy -from fwo_exceptions import FwoImporterErrorInconsistencies +from fwo_exceptions import FwoImporterErrorInconsistencies, FwoNormalizedConfigParseError -def normalize_network_objects(import_state: ImportStateController, native_config, native_config_global, normalized_config, normalized_config_global, nw_obj_types): +def normalize_network_objects(native_config, native_config_global, normalized_config, normalized_config_global, nw_obj_types): nw_objects = [] if 'objects' not in native_config: @@ -19,7 +19,7 @@ def normalize_network_objects(import_state: ImportStateController, native_config if not(current_obj_type in nw_obj_types and 'data' in native_config['objects'][current_obj_type]): continue for obj_orig in native_config['objects'][current_obj_type]['data']: - normalize_network_object(obj_orig, nw_objects, normalized_config, native_config['objects']) + normalize_network_object(obj_orig, nw_objects, normalized_config, normalized_config_global, native_config['objects']) if native_config.get('is-super-manager',False): # finally add "Original" network object for natting (only in global domain) @@ -42,7 +42,7 @@ def get_obj_member_refs_list(obj_orig, native_config_objects): f"Member inconsistent for object {obj_orig['name']}, found members={str(obj_orig['member'])} and member_refs={str(obj_member_refs_list)}") return obj_member_refs_list -def normalize_network_object(obj_orig, nw_objects, normalized_config, native_config_objects): +def normalize_network_object(obj_orig, nw_objects, normalized_config, normalized_config_global, native_config_objects): obj_zone = 'global' obj = {} obj.update({'obj_name': obj_orig['name']}) @@ -88,16 +88,20 @@ def normalize_network_object(obj_orig, nw_objects, normalized_config, native_con obj.update({'obj_uid': obj_orig.get('uuid', obj_orig['name'])}) # using name as fallback, but this should not happen - # here only picking first associated interface as zone: - if 'associated-interface' in obj_orig and len(obj_orig['associated-interface'])>0: # and obj_orig['associated-interface'][0] != 'any': - obj_zone = deepcopy(obj_orig['associated-interface'][0]) - # adding zone if it not yet exists - obj_zone = add_zone_if_missing (normalized_config, obj_zone) - obj.update({'obj_zone': obj_zone }) + associated_interfaces = object_parse_zone(obj_orig, normalized_config, normalized_config_global) + obj.update({'obj_zone': list_delimiter.join(associated_interfaces)}) - #obj.update({'control_id': import_state.ImportId}) nw_objects.append(obj) +def object_parse_zone(obj_orig, normalized_config, normalized_config_global): + associated_interfaces = [] + if 'associated-interface' in obj_orig: + for zone in obj_orig['associated-interface']: + if zone in normalized_config['zone_object'] or zone in normalized_config_global['zone_object']: + associated_interfaces.append(zone) + else: + raise FwoNormalizedConfigParseError('Could not find zone ' + zone + 'in normalized config.') + return associated_interfaces def _parse_subnet (obj, obj_orig): ipa = ipaddress.ip_network(str(obj_orig['subnet'][0]) + '/' + str(obj_orig['subnet'][1])) diff --git a/roles/importer/files/importer/fortiadom5ff/fmgr_zone.py b/roles/importer/files/importer/fortiadom5ff/fmgr_zone.py index bef8d790ea..a7ae5aca9e 100644 --- a/roles/importer/files/importer/fortiadom5ff/fmgr_zone.py +++ b/roles/importer/files/importer/fortiadom5ff/fmgr_zone.py @@ -9,42 +9,33 @@ def get_zones(sid, fm_api_url, native_config, adom_name, limit): fmgr_getter.update_config_with_fortinet_api_call( native_config['zones'], sid, fm_api_url, '/pm/config/adom/' + adom_name + '/obj/dynamic/interface', 'interface_' + adom_name, limit=limit) - # # get local zones - # for device in native_config['devices']: - # local_pkg_name = device['package'] - # for adom in native_config['adoms']: - # if adom['name']==adom_name: - # if local_pkg_name not in adom['package_names']: - # logger.error('local rulebase/package ' + local_pkg_name + ' not found in management ' + adom_name) - # return 1 - # else: - # fmgr_getter.update_config_with_fortinet_api_call( - # native_config['zones'], sid, fm_api_url, "/pm/config/adom/" + adom_name + "/obj/dynamic/interface", device['id'], debug=debug_level, limit=limit) +def normalize_zones(native_config, normalized_config_dict): + zones = [] + fetched_zones = [] + for zone_type in native_config['zones']: + for mapping in zone_type.get('data', []): + if not mapping['dynamic_mapping'] is None: + fetch_dynamic_mapping(mapping, fetched_zones) + if not mapping['platform_mapping'] is None: + fetch_platform_mapping(mapping, fetched_zones) - # native_config['zones']['zone_list'] = [] - # for device in native_config['zones']: - # for mapping in native_config['zones'][device]: - # if not isinstance(mapping, str): - # if not mapping['dynamic_mapping'] is None: - # for dyn_mapping in mapping['dynamic_mapping']: - # if 'name' in dyn_mapping and not dyn_mapping['name'] in native_config['zones']['zone_list']: - # native_config['zones']['zone_list'].append(dyn_mapping['name']) - # if 'local-intf' in dyn_mapping and not dyn_mapping['local-intf'][0] in native_config['zones']['zone_list']: - # native_config['zones']['zone_list'].append(dyn_mapping['local-intf'][0]) - # if not mapping['platform_mapping'] is None: - # for dyn_mapping in mapping['platform_mapping']: - # if 'intf-zone' in dyn_mapping and not dyn_mapping['intf-zone'] in native_config['zones']['zone_list']: - # native_config['zones']['zone_list'].append(dyn_mapping['intf-zone']) + for zone in fetched_zones: + zones.append({'zone_name': zone['name']}) + normalized_config_dict.update({'zone_objects': zones}) -def normalize_zones(full_config, config2import, import_id): - zones = [] - for orig_zone in full_config['zone_objects']['zone_list']: - zone = {} - zone.update({'zone_name': orig_zone}) - zones.append(zone) - - config2import.update({'zone_objects': zones}) +def fetch_dynamic_mapping(mapping, fetched_zones): + for dyn_mapping in mapping['dynamic_mapping']: + if 'name' in dyn_mapping and not dyn_mapping['name'] in fetched_zones: + fetched_zones.append(dyn_mapping['name']) + if 'local-intf' in dyn_mapping: + for local_interface in dyn_mapping['local-intf']: + if local_interface not in fetched_zones: + fetched_zones.append(local_interface) +def fetch_platform_mapping(mapping, fetched_zones): + for dyn_mapping in mapping['platform_mapping']: + if 'intf-zone' in dyn_mapping and not dyn_mapping['intf-zone'] in fetched_zones: + fetched_zones.append(dyn_mapping['intf-zone']) def add_zone_if_missing(normalized_config_dict: dict, zone_string): # adding zone if it not yet exists diff --git a/roles/importer/files/importer/fortiadom5ff/fwcommon.py b/roles/importer/files/importer/fortiadom5ff/fwcommon.py index 4b4b474f49..a54b5b0419 100644 --- a/roles/importer/files/importer/fortiadom5ff/fwcommon.py +++ b/roles/importer/files/importer/fortiadom5ff/fwcommon.py @@ -21,7 +21,7 @@ from fmgr_rule import normalize_rulebases, get_access_policy, get_nat_policy, normalize_nat_rulebases from fmgr_consts import nw_obj_types, svc_obj_types, user_obj_types from fwo_base import ConfigAction -from fmgr_zone import get_zones +from fmgr_zone import get_zones, normalize_zones from models.fwconfig_normalized import FwConfigNormalized @@ -184,14 +184,14 @@ def normalize_single_manager_config(native_config: dict[str, Any], native_config current_svc_obj_types = [f"svc_obj_adom/{native_config.get('domain_name','')}_{t}" for t in current_svc_obj_types] logger = getFwoLogger() - normalize_network_objects(import_state, native_config, native_config_global, normalized_config_dict, normalized_config_global, + normalize_zones(native_config, normalized_config_dict) + logger.info("completed normalizing zones for manager: " + native_config.get('domain_name','')) + normalize_network_objects(native_config, native_config_global, normalized_config_dict, normalized_config_global, current_nw_obj_types) logger.info("completed normalizing network objects for manager: " + native_config.get('domain_name','')) normalize_service_objects(import_state, native_config, native_config_global, normalized_config_dict, normalized_config_global, current_svc_obj_types) logger.info("completed normalizing service objects for manager: " + native_config.get('domain_name','')) - #fmgr_gateway.normalizeGateways(native_conf, import_state, normalized_config_dict) - mgm_uid = native_config["management_uid"] normalize_rulebases(import_state, mgm_uid, native_config, native_config_global, normalized_config_dict, normalized_config_global, is_global_loop_iteration) diff --git a/roles/importer/files/importer/fwo_exceptions.py b/roles/importer/files/importer/fwo_exceptions.py index af180129f4..625f24ec0d 100644 --- a/roles/importer/files/importer/fwo_exceptions.py +++ b/roles/importer/files/importer/fwo_exceptions.py @@ -29,6 +29,13 @@ def __init__(self, message="Login to FW management failed"): self.message = message super().__init__(self.message) +class FwoNormalizedConfigParseError(Exception): + """Raised while parsing normalized config""" + + def __init__(self, message="Parsing normalized config failed"): + self.message = message + super().__init__(self.message) + class SecretDecryptionFailed(Exception): """Raised when the attempt to decrypt a secret with the given key fails""" From faf34aab14e6413269299667a64087c883665cef Mon Sep 17 00:00:00 2001 From: alf-cactus Date: Wed, 8 Oct 2025 13:37:34 +0200 Subject: [PATCH 13/27] forti zone testing --- .../importer/fortiadom5ff/fmgr_network.py | 17 +--- .../files/importer/fortiadom5ff/fmgr_rule.py | 97 +++++++++---------- .../files/importer/fortiadom5ff/fmgr_zone.py | 31 +++--- .../files/importer/fortiadom5ff/fwcommon.py | 36 +++---- 4 files changed, 82 insertions(+), 99 deletions(-) diff --git a/roles/importer/files/importer/fortiadom5ff/fmgr_network.py b/roles/importer/files/importer/fortiadom5ff/fmgr_network.py index 54d80e8b5a..3617c1ba42 100644 --- a/roles/importer/files/importer/fortiadom5ff/fmgr_network.py +++ b/roles/importer/files/importer/fortiadom5ff/fmgr_network.py @@ -2,11 +2,11 @@ import ipaddress from fwo_log import getFwoLogger from fwo_const import list_delimiter, nat_postfix -from fmgr_zone import add_zone_if_missing +from fmgr_zone import find_zones_in_normalized_config from fwo_config import readConfig from model_controllers.import_state_controller import ImportStateController from copy import deepcopy -from fwo_exceptions import FwoImporterErrorInconsistencies, FwoNormalizedConfigParseError +from fwo_exceptions import FwoImporterErrorInconsistencies def normalize_network_objects(native_config, native_config_global, normalized_config, normalized_config_global, nw_obj_types): @@ -88,21 +88,12 @@ def normalize_network_object(obj_orig, nw_objects, normalized_config, normalized obj.update({'obj_uid': obj_orig.get('uuid', obj_orig['name'])}) # using name as fallback, but this should not happen - associated_interfaces = object_parse_zone(obj_orig, normalized_config, normalized_config_global) + associated_interfaces = find_zones_in_normalized_config( + obj_orig.get('associated-interface', []), normalized_config, normalized_config_global) obj.update({'obj_zone': list_delimiter.join(associated_interfaces)}) nw_objects.append(obj) -def object_parse_zone(obj_orig, normalized_config, normalized_config_global): - associated_interfaces = [] - if 'associated-interface' in obj_orig: - for zone in obj_orig['associated-interface']: - if zone in normalized_config['zone_object'] or zone in normalized_config_global['zone_object']: - associated_interfaces.append(zone) - else: - raise FwoNormalizedConfigParseError('Could not find zone ' + zone + 'in normalized config.') - return associated_interfaces - def _parse_subnet (obj, obj_orig): ipa = ipaddress.ip_network(str(obj_orig['subnet'][0]) + '/' + str(obj_orig['subnet'][1])) if ipa.num_addresses > 1: diff --git a/roles/importer/files/importer/fortiadom5ff/fmgr_rule.py b/roles/importer/files/importer/fortiadom5ff/fmgr_rule.py index 91e605d801..82181d662b 100644 --- a/roles/importer/files/importer/fortiadom5ff/fmgr_rule.py +++ b/roles/importer/files/importer/fortiadom5ff/fmgr_rule.py @@ -7,7 +7,7 @@ from fwo_base import extend_string_list, sanitize from fmgr_service import create_svc_object from fmgr_network import create_network_object, get_first_ip_of_destination -from fmgr_zone import add_zone_if_missing +from fmgr_zone import find_zones_in_normalized_config import fmgr_getter from fmgr_gw_networking import get_device_from_package from fwo_log import getFwoLogger @@ -32,11 +32,11 @@ def normalize_rulebases( mgm_uid: str, native_config: dict, native_config_global: dict, - normalized_config_dict: dict, + normalized_config_adom: dict, normalized_config_global: dict, is_global_loop_iteration: bool ) -> None: - normalized_config_dict['policies'] = [] + normalized_config_adom['policies'] = [] fetched_rulebase_uids: list = [] if normalized_config_global is not None: @@ -45,13 +45,13 @@ def normalize_rulebases( for gateway in native_config['gateways']: normalize_rulebases_for_each_link_destination( gateway, mgm_uid, fetched_rulebase_uids, native_config, native_config_global, - is_global_loop_iteration, normalized_config_dict, + is_global_loop_iteration, normalized_config_adom, normalized_config_global) # todo: parse nat rulebase here -def normalize_rulebases_for_each_link_destination(gateway, mgm_uid, fetched_rulebase_uids, native_config, native_config_global, is_global_loop_iteration, normalized_config_dict, normalized_config_global): +def normalize_rulebases_for_each_link_destination(gateway, mgm_uid, fetched_rulebase_uids, native_config, native_config_global, is_global_loop_iteration, normalized_config_adom, normalized_config_global): logger = getFwoLogger() for rulebase_link in gateway['rulebase_links']: if rulebase_link['to_rulebase_uid'] not in fetched_rulebase_uids and rulebase_link['to_rulebase_uid'] != '': @@ -68,13 +68,13 @@ def normalize_rulebases_for_each_link_destination(gateway, mgm_uid, fetched_rule continue normalized_rulebase = initialize_normalized_rulebase(rulebase_to_parse, mgm_uid) - parse_rulebase(normalized_config_dict, normalized_config_global, rulebase_to_parse, normalized_rulebase, found_rulebase_in_global) + parse_rulebase(normalized_config_adom, normalized_config_global, rulebase_to_parse, normalized_rulebase, found_rulebase_in_global) fetched_rulebase_uids.append(rulebase_link['to_rulebase_uid']) if found_rulebase_in_global: normalized_config_global['policies'].append(normalized_rulebase) else: - normalized_config_dict['policies'].append(normalized_rulebase) + normalized_config_adom['policies'].append(normalized_rulebase) def find_rulebase_to_parse(rulebase_list, rulebase_uid): for rulebase in rulebase_list: @@ -91,21 +91,28 @@ def initialize_normalized_rulebase(rulebase_to_parse, mgm_uid): normalized_rulebase = Rulebase(uid=rulebaseUid, name=rulebaseName, mgm_uid=mgm_uid, Rules={}) return normalized_rulebase -def parse_rulebase(normalized_config_dict, normalized_config_global, rulebase_to_parse, normalized_rulebase, found_rulebase_in_global): +def parse_rulebase(normalized_config_adom, normalized_config_global, rulebase_to_parse, normalized_rulebase, found_rulebase_in_global): rule_num = 1 for native_rule in rulebase_to_parse['data']: - rule_num = parse_single_rule(normalized_config_dict, normalized_config_global, native_rule, normalized_rulebase, rule_num) + rule_num = parse_single_rule(normalized_config_adom, normalized_config_global, native_rule, normalized_rulebase, rule_num) if not found_rulebase_in_global: - add_implicit_deny_rule(rule_num, normalized_config_dict, normalized_config_global, normalized_rulebase) + add_implicit_deny_rule(rule_num, normalized_config_adom, normalized_config_global, normalized_rulebase) -def add_implicit_deny_rule(rule_num, normalized_config_dict, normalized_config_global, rulebase: Rulebase): +def add_implicit_deny_rule(rule_num, normalized_config_adom, normalized_config_global, rulebase: Rulebase): - deny_rule = {'srcaddr': ['all'], 'srcaddr6': ['all'], 'dstaddr': ['all'], 'dstaddr6': ['all'], 'service': ['ALL']} + deny_rule = {'srcaddr': ['all'], 'srcaddr6': ['all'], + 'dstaddr': ['all'], 'dstaddr6': ['all'], + 'service': ['ALL'], + 'srcintf': ['any'], 'dstintf': ['any']} - rule_src_list, rule_src_refs_list = rule_parse_addresses(deny_rule, 'src', normalized_config_dict, normalized_config_global) - rule_dst_list, rule_dst_refs_list = rule_parse_addresses(deny_rule, 'dst', normalized_config_dict, normalized_config_global) + rule_src_list, rule_src_refs_list = rule_parse_addresses(deny_rule, 'src', normalized_config_adom, normalized_config_global) + rule_dst_list, rule_dst_refs_list = rule_parse_addresses(deny_rule, 'dst', normalized_config_adom, normalized_config_global) rule_svc_list, rule_svc_refs_list = rule_parse_service(deny_rule) + rule_src_zones = find_zones_in_normalized_config( + deny_rule.get('srcintf', []), normalized_config_adom, normalized_config_global) + rule_dst_zones = find_zones_in_normalized_config( + deny_rule.get('dstintf', []), normalized_config_adom, normalized_config_global) rule_normalized = RuleNormalized( rule_num=rule_num, @@ -133,13 +140,13 @@ def add_implicit_deny_rule(rule_num, normalized_config_dict, normalized_config_g parent_rule_uid=None, last_hit=None, rule_comment='', - rule_src_zone=None, - rule_dst_zone=rule_dst_zone, + rule_src_zone=list_delimiter.join(rule_src_zones), + rule_dst_zone=list_delimiter.join(rule_dst_zones), rule_head_text=None ) rulebase.Rules[rule_normalized.rule_uid] = rule_normalized -def parse_single_rule(normalized_config_dict, normalized_config_global, native_rule, rulebase: Rulebase, rule_num): +def parse_single_rule(normalized_config_adom, normalized_config_global, native_rule, rulebase: Rulebase, rule_num): # Extract basic rule information rule_disabled = True # Default to disabled if 'status' in native_rule and (native_rule['status'] == 1 or native_rule['status'] == 'enable'): @@ -149,12 +156,15 @@ def parse_single_rule(normalized_config_dict, normalized_config_global, native_r rule_track = rule_parse_tracking_info(native_rule) - rule_src_list, rule_src_refs_list = rule_parse_addresses(native_rule, 'src', normalized_config_dict, normalized_config_global) - rule_dst_list, rule_dst_refs_list = rule_parse_addresses(native_rule, 'dst', normalized_config_dict, normalized_config_global) + rule_src_list, rule_src_refs_list = rule_parse_addresses(native_rule, 'src', normalized_config_adom, normalized_config_global) + rule_dst_list, rule_dst_refs_list = rule_parse_addresses(native_rule, 'dst', normalized_config_adom, normalized_config_global) rule_svc_list, rule_svc_refs_list = rule_parse_service(native_rule) - rule_src_zone, rule_dst_zone = rule_parse_zone(native_rule, normalized_config_dict) + rule_src_zones = find_zones_in_normalized_config( + native_rule.get('srcintf', []), normalized_config_adom, normalized_config_global) + rule_dst_zones = find_zones_in_normalized_config( + native_rule.get('dstintf', []), normalized_config_adom, normalized_config_global) rule_src_neg, rule_dst_neg, rule_svc_neg = rule_parse_negation_flags(native_rule) rule_installon = rule_parse_installon(native_rule, rulebase.name) @@ -188,8 +198,8 @@ def parse_single_rule(normalized_config_dict, normalized_config_global, native_r parent_rule_uid=None, last_hit=last_hit, rule_comment=native_rule.get('comments'), - rule_src_zone=rule_src_zone, - rule_dst_zone=rule_dst_zone, + rule_src_zone=list_delimiter.join(rule_src_zones), + rule_dst_zone=list_delimiter.join(rule_dst_zones), rule_head_text=None ) @@ -224,46 +234,35 @@ def rule_parse_service(native_rule): return rule_svc_list, rule_svc_refs_list -def rule_parse_zone(native_rule, normalized_config_dict): - - rule_src_zone_list = [None] - if len(native_rule.get('srcintf', [])) > 0: - rule_src_zone_list = add_zone_if_missing(normalized_config_dict, native_rule['srcintf'][0]) - - rule_dst_zone_list = [None] - if len(native_rule.get('dstintf', [])) > 0: - rule_dst_zone_list = add_zone_if_missing(normalized_config_dict, native_rule['dstintf'][0]) - return rule_src_zone_list, rule_dst_zone_list - -def rule_parse_addresses(native_rule, target, normalized_config_dict, normalized_config_global): +def rule_parse_addresses(native_rule, target, normalized_config_adom, normalized_config_global): if target not in ['src', 'dst']: raise FwoImporterErrorInconsistencies(f"target '{target}' must either be src or dst.") addr_list = [] addr_ref_list = [] - build_addr_list(native_rule, True, target, normalized_config_dict, normalized_config_global, addr_list, addr_ref_list) - build_addr_list(native_rule, False, target, normalized_config_dict, normalized_config_global, addr_list, addr_ref_list) + build_addr_list(native_rule, True, target, normalized_config_adom, normalized_config_global, addr_list, addr_ref_list) + build_addr_list(native_rule, False, target, normalized_config_adom, normalized_config_global, addr_list, addr_ref_list) return addr_list, addr_ref_list -def build_addr_list(native_rule, is_v4, target, normalized_config_dict, normalized_config_global, addr_list, addr_ref_list): +def build_addr_list(native_rule, is_v4, target, normalized_config_adom, normalized_config_global, addr_list, addr_ref_list): if is_v4 and target == 'src': for addr in native_rule.get('srcaddr', []) + native_rule.get('internet-service-src-name', []): addr_list.append(addr) - addr_ref_list.append(find_addr_ref(addr, is_v4, normalized_config_dict, normalized_config_global)) + addr_ref_list.append(find_addr_ref(addr, is_v4, normalized_config_adom, normalized_config_global)) elif not is_v4 and target == 'src': for addr in native_rule.get('srcaddr6', []): addr_list.append(addr) - addr_ref_list.append(find_addr_ref(addr, is_v4, normalized_config_dict, normalized_config_global)) + addr_ref_list.append(find_addr_ref(addr, is_v4, normalized_config_adom, normalized_config_global)) elif is_v4 and target == 'dst': for addr in native_rule.get('dstaddr', []): addr_list.append(addr) - addr_ref_list.append(find_addr_ref(addr, is_v4, normalized_config_dict, normalized_config_global)) + addr_ref_list.append(find_addr_ref(addr, is_v4, normalized_config_adom, normalized_config_global)) else: for addr in native_rule.get('dstaddr6', []): addr_list.append(addr) - addr_ref_list.append(find_addr_ref(addr, is_v4, normalized_config_dict, normalized_config_global)) + addr_ref_list.append(find_addr_ref(addr, is_v4, normalized_config_adom, normalized_config_global)) -def find_addr_ref(addr, is_v4, normalized_config_dict, normalized_config_global): - for nw_obj in normalized_config_dict['network_objects'] + normalized_config_global.get('network_objects', []): +def find_addr_ref(addr, is_v4, normalized_config_adom, normalized_config_global): + for nw_obj in normalized_config_adom['network_objects'] + normalized_config_global.get('network_objects', []): if addr == nw_obj['obj_name']: if (is_v4 and ip_type(nw_obj) == 4) or (not is_v4 and ip_type(nw_obj) == 6): return nw_obj['obj_uid'] @@ -440,7 +439,7 @@ def get_nat_policy(sid, fm_api_url, native_config, adom_device_vdom_policy_packa # delete_v: ab hier kann sehr viel weg, ich lasses vorerst zB für die nat # pure nat rules -def normalize_nat_rulebases(native_config, normalized_config_dict, import_id, jwt=None): +def normalize_nat_rulebases(native_config, normalized_config_adom, import_id, jwt=None): nat_rules = [] rule_number = 0 @@ -471,9 +470,9 @@ def normalize_nat_rulebases(native_config, normalized_config_dict, import_id, jw svc_name = 'svc_' + str(rule_orig['orig-port']) # need to create a helper service object and add it to the nat rule, also needs to be added to service list - if not 'service_objects' in normalized_config_dict: # is normally defined - normalized_config_dict['service_objects'] = [] - normalized_config_dict['service_objects'].append(create_svc_object( \ + if not 'service_objects' in normalized_config_adom: # is normally defined + normalized_config_adom['service_objects'] = [] + normalized_config_adom['service_objects'].append(create_svc_object( \ import_id=import_id, name=svc_name, proto=rule_orig['protocol'], port=rule_orig['orig-port'], comment='service created by FWO importer for NAT purposes')) rule['rule_svc'] = svc_name @@ -521,7 +520,7 @@ def normalize_nat_rulebases(native_config, normalized_config_dict, import_id, jw svc_name = 'svc_' + str(rule_orig['nat-port']) # need to create a helper service object and add it to the nat rule, also needs to be added to service list! # fmgr_service.create_svc_object(name=svc_name, proto=rule_orig['protocol'], port=rule_orig['orig-port'], comment='service created by FWO importer for NAT purposes') - normalized_config_dict['service_objects'].append(create_svc_object(import_id=import_id, name=svc_name, proto=rule_orig['protocol'], port=rule_orig['nat-port'], comment='service created by FWO importer for NAT purposes')) + normalized_config_adom['service_objects'].append(create_svc_object(import_id=import_id, name=svc_name, proto=rule_orig['protocol'], port=rule_orig['nat-port'], comment='service created by FWO importer for NAT purposes')) xlate_rule['rule_svc'] = svc_name xlate_rule.update({ 'rule_src_refs': resolve_objects(xlate_rule['rule_src'], list_delimiter, native_config, 'name', 'uuid', rule_type=rule_table, jwt=jwt, import_id=import_id ) }) @@ -532,7 +531,7 @@ def normalize_nat_rulebases(native_config, normalized_config_dict, import_id, jw nat_rules.append(xlate_rule) rule_number += 1 - normalized_config_dict['rules'].extend(nat_rules) + normalized_config_adom['rules'].extend(nat_rules) def insert_header(rules, import_id, header_text, rulebase_name, rule_uid, rule_number, src_refs, dst_refs): diff --git a/roles/importer/files/importer/fortiadom5ff/fmgr_zone.py b/roles/importer/files/importer/fortiadom5ff/fmgr_zone.py index a7ae5aca9e..776e37257b 100644 --- a/roles/importer/files/importer/fortiadom5ff/fmgr_zone.py +++ b/roles/importer/files/importer/fortiadom5ff/fmgr_zone.py @@ -1,4 +1,5 @@ import fmgr_getter +from fwo_exceptions import FwoNormalizedConfigParseError def get_zones(sid, fm_api_url, native_config, adom_name, limit): @@ -9,7 +10,7 @@ def get_zones(sid, fm_api_url, native_config, adom_name, limit): fmgr_getter.update_config_with_fortinet_api_call( native_config['zones'], sid, fm_api_url, '/pm/config/adom/' + adom_name + '/obj/dynamic/interface', 'interface_' + adom_name, limit=limit) -def normalize_zones(native_config, normalized_config_dict): +def normalize_zones(native_config, normalized_config_adom): zones = [] fetched_zones = [] for zone_type in native_config['zones']: @@ -21,7 +22,7 @@ def normalize_zones(native_config, normalized_config_dict): for zone in fetched_zones: zones.append({'zone_name': zone['name']}) - normalized_config_dict.update({'zone_objects': zones}) + normalized_config_adom.update({'zone_objects': zones}) def fetch_dynamic_mapping(mapping, fetched_zones): for dyn_mapping in mapping['dynamic_mapping']: @@ -37,20 +38,12 @@ def fetch_platform_mapping(mapping, fetched_zones): if 'intf-zone' in dyn_mapping and not dyn_mapping['intf-zone'] in fetched_zones: fetched_zones.append(dyn_mapping['intf-zone']) -def add_zone_if_missing(normalized_config_dict: dict, zone_string): - # adding zone if it not yet exists - - # also transforming any into global (normalized global zone) - if zone_string == 'any': - zone_string = 'global' - if zone_string is not None: - if 'zone_objects' not in normalized_config_dict: # no zones yet? add empty zone_objects array - normalized_config_dict.update({'zone_objects': []}) - zone_exists = False - for zone in normalized_config_dict['zone_objects']: - if zone_string == zone['zone_name']: - zone_exists = True - if not zone_exists: - normalized_config_dict['zone_objects'].append({'zone_name': zone_string}) - return zone_string - \ No newline at end of file +def find_zones_in_normalized_config(native_zone_list : list, normalized_config, normalized_config_global): + """Verifies that input zones exist in normalized config""" + zone_out_list = [] + for zone in native_zone_list: + if zone in normalized_config['zone_object'] or zone in normalized_config_global['zone_object']: + zone_out_list.append(zone) + else: + raise FwoNormalizedConfigParseError('Could not find zone ' + zone + 'in normalized config.') + return zone_out_list diff --git a/roles/importer/files/importer/fortiadom5ff/fwcommon.py b/roles/importer/files/importer/fortiadom5ff/fwcommon.py index a54b5b0419..8737dfad5d 100644 --- a/roles/importer/files/importer/fortiadom5ff/fwcommon.py +++ b/roles/importer/files/importer/fortiadom5ff/fwcommon.py @@ -119,23 +119,23 @@ def normalize_config(import_state, native_config: dict[str,Any]) -> FwConfigMana rewrite_native_config_obj_type_as_key(native_config) # for easier accessability of objects in normalization process for native_conf in native_config['domains']: - normalized_config_dict = deepcopy(fwo_const.emptyNormalizedFwConfigJsonDict) + normalized_config_adom = deepcopy(fwo_const.emptyNormalizedFwConfigJsonDict) if native_conf['is-super-manager']: native_config_global = native_conf - normalized_config_global = normalized_config_dict + normalized_config_global = normalized_config_adom # delete_v: is_global_loop_iteration scheint immer False zu sein, kann dann weg - normalize_single_manager_config(native_conf, native_config_global, normalized_config_dict, normalized_config_global, + normalize_single_manager_config(native_conf, native_config_global, normalized_config_adom, normalized_config_global, import_state, is_global_loop_iteration=False) normalized_config = FwConfigNormalized( action=ConfigAction.INSERT, - network_objects=FwConfigNormalizedController.convertListToDict(normalized_config_dict.get('network_objects', []), 'obj_uid'), - service_objects=FwConfigNormalizedController.convertListToDict(normalized_config_dict.get('service_objects', []), 'svc_uid'), - zone_objects=FwConfigNormalizedController.convertListToDict(normalized_config_dict.get('zone_objects', []), 'zone_name'), - rulebases=normalized_config_dict.get('policies', []), - gateways=normalized_config_dict.get('gateways', []) + network_objects=FwConfigNormalizedController.convertListToDict(normalized_config_adom.get('network_objects', []), 'obj_uid'), + service_objects=FwConfigNormalizedController.convertListToDict(normalized_config_adom.get('service_objects', []), 'svc_uid'), + zone_objects=FwConfigNormalizedController.convertListToDict(normalized_config_adom.get('zone_objects', []), 'zone_name'), + rulebases=normalized_config_adom.get('policies', []), + gateways=normalized_config_adom.get('gateways', []) ) # TODO: identify the correct manager @@ -169,7 +169,7 @@ def rewrite_native_config_obj_type_as_key(native_config): domain['objects'] = obj_dict -def normalize_single_manager_config(native_config: dict[str, Any], native_config_global: dict[str, Any], normalized_config_dict: dict, +def normalize_single_manager_config(native_config: dict[str, Any], native_config_global: dict[str, Any], normalized_config_adom: dict, normalized_config_global: dict, import_state: ImportStateController, is_global_loop_iteration: bool): @@ -184,25 +184,25 @@ def normalize_single_manager_config(native_config: dict[str, Any], native_config current_svc_obj_types = [f"svc_obj_adom/{native_config.get('domain_name','')}_{t}" for t in current_svc_obj_types] logger = getFwoLogger() - normalize_zones(native_config, normalized_config_dict) + normalize_zones(native_config, normalized_config_adom) logger.info("completed normalizing zones for manager: " + native_config.get('domain_name','')) - normalize_network_objects(native_config, native_config_global, normalized_config_dict, normalized_config_global, + normalize_network_objects(native_config, native_config_global, normalized_config_adom, normalized_config_global, current_nw_obj_types) logger.info("completed normalizing network objects for manager: " + native_config.get('domain_name','')) - normalize_service_objects(import_state, native_config, native_config_global, normalized_config_dict, normalized_config_global, + normalize_service_objects(import_state, native_config, native_config_global, normalized_config_adom, normalized_config_global, current_svc_obj_types) logger.info("completed normalizing service objects for manager: " + native_config.get('domain_name','')) mgm_uid = native_config["management_uid"] - normalize_rulebases(import_state, mgm_uid, native_config, native_config_global, normalized_config_dict, normalized_config_global, + normalize_rulebases(import_state, mgm_uid, native_config, native_config_global, normalized_config_adom, normalized_config_global, is_global_loop_iteration) logger.info("completed normalizing rulebases for manager: " + native_config.get('domain_name','')) - normalize_nat_rulebases(import_state, mgm_uid, native_config, native_config_global, normalized_config_dict, normalized_config_global, + normalize_nat_rulebases(import_state, mgm_uid, native_config, native_config_global, normalized_config_adom, normalized_config_global, is_global_loop_iteration) - normalize_nat_rulebases(native_config, normalized_config_dict, import_id, jwt=None) + normalize_nat_rulebases(native_config, normalized_config_adom, import_id, jwt=None) logger.info("completed normalizing nat rulebases for manager: " + native_config.get('domain_name','')) - normalize_gateways(native_config, normalized_config_dict) + normalize_gateways(native_config, normalized_config_adom) def build_adom_list(importState : ImportStateController): @@ -336,7 +336,7 @@ def get_objects(sid, fm_api_url, native_config_domain, native_config_global, ado native_config_global['objects'], sid, fm_api_url, "sys/proxy/json", "nw_obj_global_firewall/internet-service-basic", limit=limit, payload=payload, method='exec') -def normalize_gateways(native_config, normalized_config_dict): +def normalize_gateways(native_config, normalized_config_adom): for gateway in native_config['gateways']: normalized_gateway = {} normalized_gateway['Uid'] = gateway['uid'] @@ -344,7 +344,7 @@ def normalize_gateways(native_config, normalized_config_dict): normalized_gateway['Interfaces'] = normalize_interfaces() normalized_gateway['Routing'] = normalize_routing() normalized_gateway['RulebaseLinks'] = normalize_links(gateway['rulebase_links']) - normalized_config_dict['gateways'].append(normalized_gateway) + normalized_config_adom['gateways'].append(normalized_gateway) def normalize_interfaces(): # TODO From acf4f93683cc10ee98b108a598ef104b0c1f6d9a Mon Sep 17 00:00:00 2001 From: alf-cactus Date: Wed, 8 Oct 2025 13:49:15 +0200 Subject: [PATCH 14/27] any zone --- .../importer/files/importer/fortiadom5ff/fmgr_zone.py | 10 ++++++---- roles/importer/files/importer/fortiadom5ff/fwcommon.py | 2 +- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/roles/importer/files/importer/fortiadom5ff/fmgr_zone.py b/roles/importer/files/importer/fortiadom5ff/fmgr_zone.py index 776e37257b..4dbe670396 100644 --- a/roles/importer/files/importer/fortiadom5ff/fmgr_zone.py +++ b/roles/importer/files/importer/fortiadom5ff/fmgr_zone.py @@ -10,9 +10,11 @@ def get_zones(sid, fm_api_url, native_config, adom_name, limit): fmgr_getter.update_config_with_fortinet_api_call( native_config['zones'], sid, fm_api_url, '/pm/config/adom/' + adom_name + '/obj/dynamic/interface', 'interface_' + adom_name, limit=limit) -def normalize_zones(native_config, normalized_config_adom): +def normalize_zones(native_config, normalized_config_adom, is_global_loop_iteration): zones = [] fetched_zones = [] + if is_global_loop_iteration: + fetched_zones.append('any') for zone_type in native_config['zones']: for mapping in zone_type.get('data', []): if not mapping['dynamic_mapping'] is None: @@ -21,7 +23,7 @@ def normalize_zones(native_config, normalized_config_adom): fetch_platform_mapping(mapping, fetched_zones) for zone in fetched_zones: - zones.append({'zone_name': zone['name']}) + zones.append({'zone_name': zone}) normalized_config_adom.update({'zone_objects': zones}) def fetch_dynamic_mapping(mapping, fetched_zones): @@ -42,8 +44,8 @@ def find_zones_in_normalized_config(native_zone_list : list, normalized_config, """Verifies that input zones exist in normalized config""" zone_out_list = [] for zone in native_zone_list: - if zone in normalized_config['zone_object'] or zone in normalized_config_global['zone_object']: + if zone in normalized_config['zone_objects'] or zone in normalized_config_global['zone_objects']: zone_out_list.append(zone) else: - raise FwoNormalizedConfigParseError('Could not find zone ' + zone + 'in normalized config.') + raise FwoNormalizedConfigParseError('Could not find zone ' + zone + ' in normalized config.') return zone_out_list diff --git a/roles/importer/files/importer/fortiadom5ff/fwcommon.py b/roles/importer/files/importer/fortiadom5ff/fwcommon.py index 8737dfad5d..3367ea7720 100644 --- a/roles/importer/files/importer/fortiadom5ff/fwcommon.py +++ b/roles/importer/files/importer/fortiadom5ff/fwcommon.py @@ -184,7 +184,7 @@ def normalize_single_manager_config(native_config: dict[str, Any], native_config current_svc_obj_types = [f"svc_obj_adom/{native_config.get('domain_name','')}_{t}" for t in current_svc_obj_types] logger = getFwoLogger() - normalize_zones(native_config, normalized_config_adom) + normalize_zones(native_config, normalized_config_adom, is_global_loop_iteration) logger.info("completed normalizing zones for manager: " + native_config.get('domain_name','')) normalize_network_objects(native_config, native_config_global, normalized_config_adom, normalized_config_global, current_nw_obj_types) From a7d9e49996d5e234499d4a1f96e40029fd837dfe Mon Sep 17 00:00:00 2001 From: alf-cactus Date: Wed, 8 Oct 2025 14:28:15 +0200 Subject: [PATCH 15/27] forti zones debug --- .../files/importer/fortiadom5ff/fmgr_network.py | 11 +++++------ .../files/importer/fortiadom5ff/fmgr_service.py | 4 ++-- .../files/importer/fortiadom5ff/fmgr_zone.py | 16 ++++++++++------ .../files/importer/fortiadom5ff/fwcommon.py | 5 +++-- 4 files changed, 20 insertions(+), 16 deletions(-) diff --git a/roles/importer/files/importer/fortiadom5ff/fmgr_network.py b/roles/importer/files/importer/fortiadom5ff/fmgr_network.py index 3617c1ba42..60b6023766 100644 --- a/roles/importer/files/importer/fortiadom5ff/fmgr_network.py +++ b/roles/importer/files/importer/fortiadom5ff/fmgr_network.py @@ -9,7 +9,7 @@ from fwo_exceptions import FwoImporterErrorInconsistencies -def normalize_network_objects(native_config, native_config_global, normalized_config, normalized_config_global, nw_obj_types): +def normalize_network_objects(native_config, native_config_global, normalized_config_adom, normalized_config_global, nw_obj_types): nw_objects = [] if 'objects' not in native_config: @@ -19,7 +19,7 @@ def normalize_network_objects(native_config, native_config_global, normalized_co if not(current_obj_type in nw_obj_types and 'data' in native_config['objects'][current_obj_type]): continue for obj_orig in native_config['objects'][current_obj_type]['data']: - normalize_network_object(obj_orig, nw_objects, normalized_config, normalized_config_global, native_config['objects']) + normalize_network_object(obj_orig, nw_objects, normalized_config_adom, normalized_config_global, native_config['objects']) if native_config.get('is-super-manager',False): # finally add "Original" network object for natting (only in global domain) @@ -28,7 +28,7 @@ def normalize_network_objects(native_config, native_config_global, normalized_co nw_objects.append(create_network_object(name=original_obj_name, type='network', ip='0.0.0.0', ip_end='255.255.255.255',\ uid=original_obj_uid, zone='global', color='black', comment='"original" network object created by FWO importer for NAT purposes')) - normalized_config.update({'network_objects': nw_objects}) + normalized_config_adom.update({'network_objects': nw_objects}) def get_obj_member_refs_list(obj_orig, native_config_objects): obj_member_refs_list = [] @@ -42,8 +42,7 @@ def get_obj_member_refs_list(obj_orig, native_config_objects): f"Member inconsistent for object {obj_orig['name']}, found members={str(obj_orig['member'])} and member_refs={str(obj_member_refs_list)}") return obj_member_refs_list -def normalize_network_object(obj_orig, nw_objects, normalized_config, normalized_config_global, native_config_objects): - obj_zone = 'global' +def normalize_network_object(obj_orig, nw_objects, normalized_config_adom, normalized_config_global, native_config_objects): obj = {} obj.update({'obj_name': obj_orig['name']}) if 'subnet' in obj_orig: # ipv4 object @@ -89,7 +88,7 @@ def normalize_network_object(obj_orig, nw_objects, normalized_config, normalized obj.update({'obj_uid': obj_orig.get('uuid', obj_orig['name'])}) # using name as fallback, but this should not happen associated_interfaces = find_zones_in_normalized_config( - obj_orig.get('associated-interface', []), normalized_config, normalized_config_global) + obj_orig.get('associated-interface', []), normalized_config_adom, normalized_config_global) obj.update({'obj_zone': list_delimiter.join(associated_interfaces)}) nw_objects.append(obj) diff --git a/roles/importer/files/importer/fortiadom5ff/fmgr_service.py b/roles/importer/files/importer/fortiadom5ff/fmgr_service.py index 8d806765c8..c54f75c840 100644 --- a/roles/importer/files/importer/fortiadom5ff/fmgr_service.py +++ b/roles/importer/files/importer/fortiadom5ff/fmgr_service.py @@ -4,7 +4,7 @@ from fwo_log import getFwoLogger from typing import Any -def normalize_service_objects(import_state: ImportStateController, native_config, native_config_global, normalized_config, +def normalize_service_objects(import_state: ImportStateController, native_config, native_config_global, normalized_config_adom, normalized_config_global, svc_obj_types): svc_objects = [] logger = getFwoLogger() @@ -23,7 +23,7 @@ def normalize_service_objects(import_state: ImportStateController, native_config svc_objects.append(create_svc_object(name=original_obj_name, proto=0, color='foreground', port=None,\ comment='"original" service object created by FWO importer for NAT purposes')) - normalized_config.update({'service_objects': svc_objects}) + normalized_config_adom.update({'service_objects': svc_objects}) def normalize_service_object(obj_orig, svc_objects): member_names = '' diff --git a/roles/importer/files/importer/fortiadom5ff/fmgr_zone.py b/roles/importer/files/importer/fortiadom5ff/fmgr_zone.py index 4dbe670396..263f37e52e 100644 --- a/roles/importer/files/importer/fortiadom5ff/fmgr_zone.py +++ b/roles/importer/files/importer/fortiadom5ff/fmgr_zone.py @@ -40,12 +40,16 @@ def fetch_platform_mapping(mapping, fetched_zones): if 'intf-zone' in dyn_mapping and not dyn_mapping['intf-zone'] in fetched_zones: fetched_zones.append(dyn_mapping['intf-zone']) -def find_zones_in_normalized_config(native_zone_list : list, normalized_config, normalized_config_global): +def find_zones_in_normalized_config(native_zone_list : list, normalized_config_adom, normalized_config_global): """Verifies that input zones exist in normalized config""" zone_out_list = [] - for zone in native_zone_list: - if zone in normalized_config['zone_objects'] or zone in normalized_config_global['zone_objects']: - zone_out_list.append(zone) - else: - raise FwoNormalizedConfigParseError('Could not find zone ' + zone + ' in normalized config.') + for nativ_zone in native_zone_list: + was_zone_found = False + for normalized_zone in normalized_config_adom['zone_objects'] + normalized_config_global['zone_objects']: + if nativ_zone == normalized_zone['zone_name']: + zone_out_list.append(normalized_zone['zone_name']) + was_zone_found = True + break + if not was_zone_found: + raise FwoNormalizedConfigParseError('Could not find zone ' + nativ_zone + ' in normalized config.') return zone_out_list diff --git a/roles/importer/files/importer/fortiadom5ff/fwcommon.py b/roles/importer/files/importer/fortiadom5ff/fwcommon.py index 3367ea7720..2a6cad875b 100644 --- a/roles/importer/files/importer/fortiadom5ff/fwcommon.py +++ b/roles/importer/files/importer/fortiadom5ff/fwcommon.py @@ -120,14 +120,15 @@ def normalize_config(import_state, native_config: dict[str,Any]) -> FwConfigMana for native_conf in native_config['domains']: normalized_config_adom = deepcopy(fwo_const.emptyNormalizedFwConfigJsonDict) + is_global_loop_iteration = False if native_conf['is-super-manager']: native_config_global = native_conf normalized_config_global = normalized_config_adom + is_global_loop_iteration = True - # delete_v: is_global_loop_iteration scheint immer False zu sein, kann dann weg normalize_single_manager_config(native_conf, native_config_global, normalized_config_adom, normalized_config_global, - import_state, is_global_loop_iteration=False) + import_state, is_global_loop_iteration) normalized_config = FwConfigNormalized( action=ConfigAction.INSERT, From 5b9e4b82ddaf82849801486af63f6b89714180f2 Mon Sep 17 00:00:00 2001 From: alf-cactus Date: Wed, 8 Oct 2025 15:27:54 +0200 Subject: [PATCH 16/27] forti debug --- roles/importer/files/importer/fortiadom5ff/fmgr_rule.py | 2 +- roles/importer/files/importer/fortiadom5ff/fmgr_zone.py | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/roles/importer/files/importer/fortiadom5ff/fmgr_rule.py b/roles/importer/files/importer/fortiadom5ff/fmgr_rule.py index 82181d662b..68239972ec 100644 --- a/roles/importer/files/importer/fortiadom5ff/fmgr_rule.py +++ b/roles/importer/files/importer/fortiadom5ff/fmgr_rule.py @@ -133,7 +133,7 @@ def add_implicit_deny_rule(rule_num, normalized_config_adom, normalized_config_g rule_time='', # Time-based rules not commonly used in basic Fortinet configs rule_name='Implicit Deny', rule_uid='', - rule_custom_fields={}, + rule_custom_fields=str({}), rule_implied=False, rule_type=RuleType.ACCESS, last_change_admin='', diff --git a/roles/importer/files/importer/fortiadom5ff/fmgr_zone.py b/roles/importer/files/importer/fortiadom5ff/fmgr_zone.py index 263f37e52e..d6640f369a 100644 --- a/roles/importer/files/importer/fortiadom5ff/fmgr_zone.py +++ b/roles/importer/files/importer/fortiadom5ff/fmgr_zone.py @@ -13,8 +13,9 @@ def get_zones(sid, fm_api_url, native_config, adom_name, limit): def normalize_zones(native_config, normalized_config_adom, is_global_loop_iteration): zones = [] fetched_zones = [] - if is_global_loop_iteration: + if is_global_loop_iteration: # can not find the following zones in api return fetched_zones.append('any') + fetched_zones.append('sslvpn_tun_intf') for zone_type in native_config['zones']: for mapping in zone_type.get('data', []): if not mapping['dynamic_mapping'] is None: From dbef0a2814088d6dd99a15cf6d1825bf84a33b4a Mon Sep 17 00:00:00 2001 From: alf-cactus Date: Thu, 16 Oct 2025 11:09:52 +0200 Subject: [PATCH 17/27] nat wip --- .../importer/fortiadom5ff/fmgr_consts.py | 4 +- .../files/importer/fortiadom5ff/fmgr_rule.py | 43 +++++++++++-------- .../files/importer/fortiadom5ff/fmgr_zone.py | 2 + 3 files changed, 29 insertions(+), 20 deletions(-) diff --git a/roles/importer/files/importer/fortiadom5ff/fmgr_consts.py b/roles/importer/files/importer/fortiadom5ff/fmgr_consts.py index ba810971ac..44127b2e69 100644 --- a/roles/importer/files/importer/fortiadom5ff/fmgr_consts.py +++ b/roles/importer/files/importer/fortiadom5ff/fmgr_consts.py @@ -2,10 +2,12 @@ nw_obj_types = ['firewall/address', 'firewall/address6', 'firewall/addrgrp', 'firewall/addrgrp6', 'firewall/ippool', 'firewall/vip', 'system/external-resource', 'firewall/wildcard-fqdn/custom', 'firewall/wildcard-fqdn/group'] -# TODO add + svc_obj_types = ['application/list', 'application/group', 'application/categories', 'application/custom', 'firewall/service/custom', 'firewall/service/group'] +nat_types = ['central/dnat', 'central/dnat6', 'firewall/central-snat-map'] + # delte_v: beide typen können weg # v4_object_types = ['nw_obj_global_firewall/address', 'nw_obj_global_firewall/addrgrp'] # v6_object_types = ['nw_obj_adom_firewall/address', 'nw_obj_adom_firewall/addrgrp','nw_obj_global_firewall/address', \ diff --git a/roles/importer/files/importer/fortiadom5ff/fmgr_rule.py b/roles/importer/files/importer/fortiadom5ff/fmgr_rule.py index 68239972ec..4759478105 100644 --- a/roles/importer/files/importer/fortiadom5ff/fmgr_rule.py +++ b/roles/importer/files/importer/fortiadom5ff/fmgr_rule.py @@ -8,6 +8,7 @@ from fmgr_service import create_svc_object from fmgr_network import create_network_object, get_first_ip_of_destination from fmgr_zone import find_zones_in_normalized_config +from fmgr_consts import nat_types import fmgr_getter from fmgr_gw_networking import get_device_from_package from fwo_log import getFwoLogger @@ -76,6 +77,16 @@ def normalize_rulebases_for_each_link_destination(gateway, mgm_uid, fetched_rule else: normalized_config_adom['policies'].append(normalized_rulebase) + normalize_nat_rulebase() + +def normalize_nat_rulebase(rulebase_link): + if not rulebase_link['is_section']: + check_if_nat_rulebases_exist() + +def check_if_nat_rulebases_exist(): + for nat_type in nat_types: + nat_type_string + def find_rulebase_to_parse(rulebase_list, rulebase_uid): for rulebase in rulebase_list: if rulebase['uid'] == rulebase_uid: @@ -373,7 +384,7 @@ def is_rulebase_already_fetched(rulebases, type): def link_rulebase(link_list, rulebases, pkg_name, rulebase_type_prefix, previous_rulebase, is_global): for version in ['v4', 'v6']: full_pkg_name = rulebase_type_prefix + '_' + version + '_' + pkg_name - has_data = has_rulebase_data(rulebases, full_pkg_name, is_global) + has_data = has_rulebase_data(rulebases, full_pkg_name, is_global, version, pkg_name) if has_data: link_list.append(build_link(previous_rulebase, full_pkg_name, is_global)) previous_rulebase = full_pkg_name @@ -395,13 +406,21 @@ def build_link(previous_rulebase, full_pkg_name, is_global): 'is_section': False } -def has_rulebase_data(rulebases, full_pkg_name, is_global): +def has_rulebase_data(rulebases, full_pkg_name, is_global, version, pkg_name): + # delete_v: hier nach urlaub weiter mit nat, gerade die letzten 3 keys hinzugefügt """adds name and uid to rulebase and removes empty global rulebases""" has_data = False + if version == 'v4': + is_v4 = True + else: + is_v4 = False for rulebase in rulebases: if rulebase['type'] == full_pkg_name: rulebase.update({'name': full_pkg_name, - 'uid': full_pkg_name}) + 'uid': full_pkg_name, + 'is_global': is_global, + 'is_v4': is_v4, + 'package': pkg_name}) if len(rulebase['data']) > 0: has_data = True elif is_global: @@ -411,32 +430,18 @@ def has_rulebase_data(rulebases, full_pkg_name, is_global): def get_nat_policy(sid, fm_api_url, native_config, adom_device_vdom_policy_package_structure, adom_name, mgm_details_device, limit): local_pkg_name, global_pkg_name = find_packages(adom_device_vdom_policy_package_structure, adom_name, mgm_details_device) if adom_name == '': - for nat_type in ['central/dnat', 'central/dnat6', 'firewall/central-snat-map']: + for nat_type in nat_types: fmgr_getter.update_config_with_fortinet_api_call( native_config['nat_rulebases'], sid, fm_api_url, '/pm/config/global/pkg/' + global_pkg_name + '/' + nat_type, nat_type + '_global_' + global_pkg_name, limit=limit) else: - for nat_type in ['central/dnat', 'central/dnat6', 'firewall/central-snat-map']: + for nat_type in nat_types: fmgr_getter.update_config_with_fortinet_api_call( native_config['nat_rulebases'], sid, fm_api_url, '/pm/config/adom/' + adom_name + '/pkg/' + local_pkg_name + '/' + nat_type, nat_type + '_adom_' + adom_name + '_' + local_pkg_name, limit=limit) - # scope = 'global' - # pkg = device['global_rulebase_name'] - # if pkg is not None and pkg != '': # only read global rulebase if it exists - # for nat_type in ['central/dnat', 'central/dnat6', 'firewall/central-snat-map']: - # fmgr_getter.update_config_with_fortinet_api_call( - # nativeConfig['rules_global_nat'], sid, fm_api_url, "/pm/config/" + scope + "/pkg/" + pkg + '/' + nat_type, device['local_rulebase_name'], limit=limit) - - # scope = 'adom/'+adom_name - # pkg = device['local_rulebase_name'] - # for nat_type in ['central/dnat', 'central/dnat6', 'firewall/central-snat-map']: - # fmgr_getter.update_config_with_fortinet_api_call( - # nativeConfig['rules_adom_nat'], sid, fm_api_url, "/pm/config/" + scope + "/pkg/" + pkg + '/' + nat_type, device['local_rulebase_name'], limit=limit) - - # delete_v: ab hier kann sehr viel weg, ich lasses vorerst zB für die nat # pure nat rules def normalize_nat_rulebases(native_config, normalized_config_adom, import_id, jwt=None): diff --git a/roles/importer/files/importer/fortiadom5ff/fmgr_zone.py b/roles/importer/files/importer/fortiadom5ff/fmgr_zone.py index d6640f369a..08fed159d3 100644 --- a/roles/importer/files/importer/fortiadom5ff/fmgr_zone.py +++ b/roles/importer/files/importer/fortiadom5ff/fmgr_zone.py @@ -18,6 +18,8 @@ def normalize_zones(native_config, normalized_config_adom, is_global_loop_iterat fetched_zones.append('sslvpn_tun_intf') for zone_type in native_config['zones']: for mapping in zone_type.get('data', []): + if 'defmap-intf' in mapping and not mapping['defmap-intf'] in fetched_zones: + fetched_zones.append(mapping['defmap-intf']) if not mapping['dynamic_mapping'] is None: fetch_dynamic_mapping(mapping, fetched_zones) if not mapping['platform_mapping'] is None: From 35024a738358a124c6f6d8a57860c92116a11ce3 Mon Sep 17 00:00:00 2001 From: alf-cactus Date: Thu, 16 Oct 2025 19:21:27 +0200 Subject: [PATCH 18/27] nat wip, but ready for pr --- roles/importer/files/importer/fortiadom5ff/fmgr_rule.py | 3 ++- roles/importer/files/importer/fwo_const.py | 1 + .../files/importer/model_controllers/check_consistency.py | 4 ++-- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/roles/importer/files/importer/fortiadom5ff/fmgr_rule.py b/roles/importer/files/importer/fortiadom5ff/fmgr_rule.py index 4759478105..aefeef1a10 100644 --- a/roles/importer/files/importer/fortiadom5ff/fmgr_rule.py +++ b/roles/importer/files/importer/fortiadom5ff/fmgr_rule.py @@ -85,7 +85,8 @@ def normalize_nat_rulebase(rulebase_link): def check_if_nat_rulebases_exist(): for nat_type in nat_types: - nat_type_string + # delete_v: hier weiter nat_type_string + pass def find_rulebase_to_parse(rulebase_list, rulebase_uid): for rulebase in rulebase_list: diff --git a/roles/importer/files/importer/fwo_const.py b/roles/importer/files/importer/fwo_const.py index d2d657fdb4..8bd2536bb3 100644 --- a/roles/importer/files/importer/fwo_const.py +++ b/roles/importer/files/importer/fwo_const.py @@ -39,6 +39,7 @@ api_call_chunk_size = 1000 rule_num_numeric_steps = 1024.0 +# TODO replace rules with policies, breaks all importers except forti and cp emptyNormalizedFwConfigJsonDict = { 'network_objects': [], 'service_objects': [], diff --git a/roles/importer/files/importer/model_controllers/check_consistency.py b/roles/importer/files/importer/model_controllers/check_consistency.py index b70b5ffb5b..361153b98c 100644 --- a/roles/importer/files/importer/model_controllers/check_consistency.py +++ b/roles/importer/files/importer/model_controllers/check_consistency.py @@ -267,9 +267,9 @@ def _collect_zone_refs_from_rules(single_config): for rb in single_config.rulebases: for rule_id in rb.Rules: if rb.Rules[rule_id].rule_src_zone is not None: - all_used_zones_refs.append(rb.Rules[rule_id].rule_src_zone) + all_used_zones_refs.extend(rb.Rules[rule_id].rule_src_zone.split(fwo_const.line_delimiter)) if rb.Rules[rule_id].rule_dst_zone is not None: - all_used_zones_refs.append(rb.Rules[rule_id].rule_dst_zone) + all_used_zones_refs.extend(rb.Rules[rule_id].rule_dst_zone.split(fwo_const.line_delimiter)) return set(all_used_zones_refs) From 5a96263dcf509953c98d4eac6efa17421dc450ad Mon Sep 17 00:00:00 2001 From: alf-cactus Date: Fri, 17 Oct 2025 09:13:31 +0200 Subject: [PATCH 19/27] sonarcube --- roles/importer/files/importer/fortiadom5ff/fmgr_rule.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/roles/importer/files/importer/fortiadom5ff/fmgr_rule.py b/roles/importer/files/importer/fortiadom5ff/fmgr_rule.py index aefeef1a10..947d73d1fe 100644 --- a/roles/importer/files/importer/fortiadom5ff/fmgr_rule.py +++ b/roles/importer/files/importer/fortiadom5ff/fmgr_rule.py @@ -77,7 +77,7 @@ def normalize_rulebases_for_each_link_destination(gateway, mgm_uid, fetched_rule else: normalized_config_adom['policies'].append(normalized_rulebase) - normalize_nat_rulebase() + #normalize_nat_rulebase(rulebase_link) def normalize_nat_rulebase(rulebase_link): if not rulebase_link['is_section']: From d74eeb0163ee6a5bb311942070d85c174ef5fbed Mon Sep 17 00:00:00 2001 From: alf-cactus Date: Fri, 17 Oct 2025 09:33:52 +0200 Subject: [PATCH 20/27] deactivate nat for now --- roles/importer/files/importer/fortiadom5ff/fwcommon.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/roles/importer/files/importer/fortiadom5ff/fwcommon.py b/roles/importer/files/importer/fortiadom5ff/fwcommon.py index 2a6cad875b..8755abddf7 100644 --- a/roles/importer/files/importer/fortiadom5ff/fwcommon.py +++ b/roles/importer/files/importer/fortiadom5ff/fwcommon.py @@ -198,10 +198,10 @@ def normalize_single_manager_config(native_config: dict[str, Any], native_config is_global_loop_iteration) logger.info("completed normalizing rulebases for manager: " + native_config.get('domain_name','')) - normalize_nat_rulebases(import_state, mgm_uid, native_config, native_config_global, normalized_config_adom, normalized_config_global, - is_global_loop_iteration) - normalize_nat_rulebases(native_config, normalized_config_adom, import_id, jwt=None) - logger.info("completed normalizing nat rulebases for manager: " + native_config.get('domain_name','')) + #normalize_nat_rulebases(import_state, mgm_uid, native_config, native_config_global, normalized_config_adom, normalized_config_global, + # is_global_loop_iteration) + #normalize_nat_rulebases(native_config, normalized_config_adom, import_id, jwt=None) + #logger.info("completed normalizing nat rulebases for manager: " + native_config.get('domain_name','')) normalize_gateways(native_config, normalized_config_adom) From b64ef136d7e2ff12457b5dbe45f3b74c6b29f348 Mon Sep 17 00:00:00 2001 From: alf-cactus Date: Thu, 23 Oct 2025 15:28:25 +0200 Subject: [PATCH 21/27] forti nat wip --- .../files/importer/checkpointR8x/cp_rule.py | 2 +- .../files/importer/fortiadom5ff/fmgr_rule.py | 238 ++++++++++-------- .../files/importer/fortiadom5ff/fwcommon.py | 3 +- 3 files changed, 137 insertions(+), 106 deletions(-) diff --git a/roles/importer/files/importer/checkpointR8x/cp_rule.py b/roles/importer/files/importer/checkpointR8x/cp_rule.py index 32c33ea06c..63e90ad425 100644 --- a/roles/importer/files/importer/checkpointR8x/cp_rule.py +++ b/roles/importer/files/importer/checkpointR8x/cp_rule.py @@ -24,7 +24,7 @@ - migrate section headers from rule to ordering element ... """ -def normalize_rulebases (nativeConfig, native_config_global, importState, normalized_config_dict, +def normalize_rulebases(nativeConfig, native_config_global, importState, normalized_config_dict, normalized_config_global, is_global_loop_iteration): normalized_config_dict['policies'] = [] diff --git a/roles/importer/files/importer/fortiadom5ff/fmgr_rule.py b/roles/importer/files/importer/fortiadom5ff/fmgr_rule.py index c3754a397d..17f0f499e5 100644 --- a/roles/importer/files/importer/fortiadom5ff/fmgr_rule.py +++ b/roles/importer/files/importer/fortiadom5ff/fmgr_rule.py @@ -1,24 +1,19 @@ import copy import jsonpickle import ipaddress -import time from time import strftime, localtime from fwo_const import list_delimiter, nat_postfix, dummy_ip -from fwo_base import extend_string_list, sanitize +from fwo_base import extend_string_list from fmgr_service import create_svc_object from fmgr_network import create_network_object, get_first_ip_of_destination from fmgr_zone import find_zones_in_normalized_config from fmgr_consts import nat_types import fmgr_getter -from fmgr_gw_networking import get_device_from_package from fwo_log import getFwoLogger from model_controllers.route_controller import get_matching_route_obj, get_ip_of_interface_obj from fwo_exceptions import FwoDeviceWithoutLocalPackage, FwoImporterErrorInconsistencies -#from fmgr_base import resolve_raw_objects, resolve_objects -from models.rule import Rule, RuleNormalized, RuleAction, RuleTrack, RuleType +from models.rule import RuleNormalized, RuleAction, RuleTrack, RuleType from models.rulebase import Rulebase -from models.import_state import ImportState - NETWORK_OBJECT='network_object' rule_access_scope_v4 = ['rules_global_header_v4', 'rules_adom_v4', 'rules_global_footer_v4'] @@ -29,7 +24,6 @@ def normalize_rulebases( - import_state: ImportState, mgm_uid: str, native_config: dict, native_config_global: dict, @@ -52,7 +46,8 @@ def normalize_rulebases( # todo: parse nat rulebase here -def normalize_rulebases_for_each_link_destination(gateway, mgm_uid, fetched_rulebase_uids, native_config, native_config_global, is_global_loop_iteration, normalized_config_adom, normalized_config_global): +def normalize_rulebases_for_each_link_destination(gateway, mgm_uid, fetched_rulebase_uids, native_config, + native_config_global, is_global_loop_iteration, normalized_config_adom, normalized_config_global): logger = getFwoLogger() for rulebase_link in gateway['rulebase_links']: if rulebase_link['to_rulebase_uid'] not in fetched_rulebase_uids and rulebase_link['to_rulebase_uid'] != '': @@ -77,16 +72,22 @@ def normalize_rulebases_for_each_link_destination(gateway, mgm_uid, fetched_rule else: normalized_config_adom['policies'].append(normalized_rulebase) - #normalize_nat_rulebase(rulebase_link) + normalize_nat_rulebase(rulebase_link, native_config) -def normalize_nat_rulebase(rulebase_link): +def normalize_nat_rulebase(rulebase_link, native_config): if not rulebase_link['is_section']: - check_if_nat_rulebases_exist() + for nat_type in nat_types: + nat_type_string = nat_type + '_' + rulebase_link['to_rulebase_uid'] + nat_rulebase = get_native_nat_rulebase(native_config, nat_type_string) + parse_nat_rulebase(nat_rulebase) -def check_if_nat_rulebases_exist(): - for nat_type in nat_types: - # delete_v: hier weiter nat_type_string - pass +def get_native_nat_rulebase(native_config, nat_type_string): + logger = getFwoLogger() + for nat_rulebase in native_config['nat_rulebases']: + if nat_type_string == nat_rulebase['type']: + return nat_rulebase['data'] + logger.warning('no nat data for '+ nat_type_string) + return [] def find_rulebase_to_parse(rulebase_list, rulebase_uid): for rulebase in rulebase_list: @@ -415,7 +416,6 @@ def build_link(previous_rulebase, full_pkg_name, is_global): } def has_rulebase_data(rulebases, full_pkg_name, is_global, version, pkg_name): - # delete_v: hier nach urlaub weiter mit nat, gerade die letzten 3 keys hinzugefügt """adds name and uid to rulebase and removes empty global rulebases""" has_data = False if version == 'v4': @@ -452,98 +452,130 @@ def get_nat_policy(sid, fm_api_url, native_config, adom_device_vdom_policy_packa # delete_v: ab hier kann sehr viel weg, ich lasses vorerst zB für die nat # pure nat rules -def normalize_nat_rulebases(native_config, normalized_config_adom, import_id, jwt=None): +def parse_nat_rulebase(nat_rulebase): #(nat_rulebase, normalized_config_adom, import_id, jwt=None): nat_rules = [] rule_number = 0 + for rule_orig in nat_rulebase: + + + rule_normalized = RuleNormalized( + rule_num=rule_number, + rule_num_numeric=0, + rule_disabled=False, + rule_src_neg=False, + rule_src=list_delimiter.join(rule_src_list), + rule_src_refs=list_delimiter.join(rule_src_refs_list), + rule_dst_neg=False, + rule_dst=list_delimiter.join(rule_dst_list), + rule_dst_refs=list_delimiter.join(rule_dst_refs_list), + rule_svc_neg=False, + rule_svc=list_delimiter.join(rule_svc_list), + rule_svc_refs=list_delimiter.join(rule_svc_refs_list), + rule_action=rule_action, + rule_track=rule_track, + rule_installon=rule_installon, + rule_time='', # Time-based rules not commonly used in basic Fortinet configs + rule_name=native_rule.get('name'), + rule_uid=native_rule.get('uuid'), + rule_custom_fields=str(native_rule.get('meta fields', {})), + rule_implied=False, + rule_type=RuleType.ACCESS, + last_change_admin=native_rule.get('_last-modified-by', ''), + parent_rule_uid=None, + last_hit=last_hit, + rule_comment=native_rule.get('comments'), + rule_src_zone=list_delimiter.join(rule_src_zones), + rule_dst_zone=list_delimiter.join(rule_dst_zones), + rule_head_text=None + ) + - for rule_table in rule_nat_scope: - for localPkgName in native_config['rules_global_nat']: - for rule_orig in native_config[rule_table][localPkgName]: - rule = {'rule_src': '', 'rule_dst': '', 'rule_svc': ''} - if rule_orig['nat'] == 1: # assuming source nat - rule.update({ 'control_id': import_id}) - rule.update({ 'rulebase_name': localPkgName}) # the rulebase_name just has to be a unique string among devices - rule.update({ 'rule_ruleid': rule_orig['policyid']}) - rule.update({ 'rule_uid': rule_orig['uuid']}) - # rule.update({ 'rule_num': rule_orig['obj seq']}) - rule.update({ 'rule_num': rule_number }) - if 'comments' in rule_orig: - rule.update({ 'rule_comment': rule_orig['comments']}) - rule.update({ 'rule_action': 'Drop' }) # not used for nat rules - rule.update({ 'rule_track': 'None'}) # not used for nat rules - - rule['rule_src'] = extend_string_list(rule['rule_src'], rule_orig, 'orig-addr', list_delimiter, jwt=jwt, import_id=import_id) - rule['rule_dst'] = extend_string_list(rule['rule_dst'], rule_orig, 'dst-addr', list_delimiter, jwt=jwt, import_id=import_id) - - if rule_orig['protocol']==17: - svc_name = 'udp_' + str(rule_orig['orig-port']) - elif rule_orig['protocol']==6: - svc_name = 'tcp_' + str(rule_orig['orig-port']) - else: - svc_name = 'svc_' + str(rule_orig['orig-port']) - # need to create a helper service object and add it to the nat rule, also needs to be added to service list - - if not 'service_objects' in normalized_config_adom: # is normally defined - normalized_config_adom['service_objects'] = [] - normalized_config_adom['service_objects'].append(create_svc_object( \ - import_id=import_id, name=svc_name, proto=rule_orig['protocol'], port=rule_orig['orig-port'], comment='service created by FWO importer for NAT purposes')) - rule['rule_svc'] = svc_name - - #rule['rule_src'] = extend_string_list(rule['rule_src'], rule_orig, 'srcaddr6', list_delimiter, jwt=jwt, import_id=import_id) - #rule['rule_dst'] = extend_string_list(rule['rule_dst'], rule_orig, 'dstaddr6', list_delimiter, jwt=jwt, import_id=import_id) - - if len(rule_orig['srcintf'])>0: - rule.update({ 'rule_from_zone': rule_orig['srcintf'][0] }) # todo: currently only using the first zone - if len(rule_orig['dstintf'])>0: - rule.update({ 'rule_to_zone': rule_orig['dstintf'][0] }) # todo: currently only using the first zone - - rule.update({ 'rule_src_neg': False}) - rule.update({ 'rule_dst_neg': False}) - rule.update({ 'rule_svc_neg': False}) - rule.update({ 'rule_src_refs': resolve_raw_objects(rule['rule_src'], list_delimiter, native_config, 'name', 'uuid', rule_type=rule_table) }, \ - jwt=jwt, import_id=import_id, rule_uid=rule_orig['uuid'], object_type=NETWORK_OBJECT) - rule.update({ 'rule_dst_refs': resolve_raw_objects(rule['rule_dst'], list_delimiter, native_config, 'name', 'uuid', rule_type=rule_table) }, \ - jwt=jwt, import_id=import_id, rule_uid=rule_orig['uuid'], object_type=NETWORK_OBJECT) - # services do not have uids, so using name instead - rule.update({ 'rule_svc_refs': rule['rule_svc'] }) - rule.update({ 'rule_type': 'original' }) - rule.update({ 'rule_installon': localPkgName }) - if 'status' in rule_orig and (rule_orig['status']=='enable' or rule_orig['status']==1): - rule.update({ 'rule_disabled': False }) - else: - rule.update({ 'rule_disabled': True }) - rule.update({ 'rule_implied': False }) - rule.update({ 'rule_time': None }) - rule.update({ 'parent_rule_id': None }) - - nat_rules.append(rule) - add_users_to_rule(rule_orig, rule) - - ############## now adding the xlate rule part ########################## - xlate_rule = dict(rule) # copy the original (match) rule - xlate_rule.update({'rule_src': '', 'rule_dst': '', 'rule_svc': ''}) - xlate_rule['rule_src'] = extend_string_list(xlate_rule['rule_src'], rule_orig, 'orig-addr', list_delimiter, jwt=jwt, import_id=import_id) - xlate_rule['rule_dst'] = 'Original' - - if rule_orig['protocol']==17: - svc_name = 'udp_' + str(rule_orig['nat-port']) - elif rule_orig['protocol']==6: - svc_name = 'tcp_' + str(rule_orig['nat-port']) - else: - svc_name = 'svc_' + str(rule_orig['nat-port']) - # need to create a helper service object and add it to the nat rule, also needs to be added to service list! - # fmgr_service.create_svc_object(name=svc_name, proto=rule_orig['protocol'], port=rule_orig['orig-port'], comment='service created by FWO importer for NAT purposes') - normalized_config_adom['service_objects'].append(create_svc_object(import_id=import_id, name=svc_name, proto=rule_orig['protocol'], port=rule_orig['nat-port'], comment='service created by FWO importer for NAT purposes')) - xlate_rule['rule_svc'] = svc_name + # for rule_table in rule_nat_scope: + # for localPkgName in native_config['rules_global_nat']: + for rule_orig in nat_rulebase: + rule = {'rule_src': '', 'rule_dst': '', 'rule_svc': ''} + if rule_orig['nat'] == 1: # assuming source nat + rule.update({ 'rule_ruleid': rule_orig['policyid']}) + rule.update({ 'rule_uid': rule_orig['uuid']}) + # rule.update({ 'rule_num': rule_orig['obj seq']}) + rule.update({ 'rule_num': rule_number }) + if 'comments' in rule_orig: + rule.update({ 'rule_comment': rule_orig['comments']}) + rule.update({ 'rule_action': 'Drop' }) # not used for nat rules + rule.update({ 'rule_track': 'None'}) # not used for nat rules + + rule['rule_src'] = extend_string_list(rule['rule_src'], rule_orig, 'orig-addr', list_delimiter, jwt=jwt, import_id=import_id) + rule['rule_dst'] = extend_string_list(rule['rule_dst'], rule_orig, 'dst-addr', list_delimiter, jwt=jwt, import_id=import_id) + + if rule_orig['protocol']==17: + svc_name = 'udp_' + str(rule_orig['orig-port']) + elif rule_orig['protocol']==6: + svc_name = 'tcp_' + str(rule_orig['orig-port']) + else: + svc_name = 'svc_' + str(rule_orig['orig-port']) + # need to create a helper service object and add it to the nat rule, also needs to be added to service list + + if not 'service_objects' in normalized_config_adom: # is normally defined + normalized_config_adom['service_objects'] = [] + normalized_config_adom['service_objects'].append(create_svc_object( \ + import_id=import_id, name=svc_name, proto=rule_orig['protocol'], port=rule_orig['orig-port'], comment='service created by FWO importer for NAT purposes')) + rule['rule_svc'] = svc_name + + #rule['rule_src'] = extend_string_list(rule['rule_src'], rule_orig, 'srcaddr6', list_delimiter, jwt=jwt, import_id=import_id) + #rule['rule_dst'] = extend_string_list(rule['rule_dst'], rule_orig, 'dstaddr6', list_delimiter, jwt=jwt, import_id=import_id) + + if len(rule_orig['srcintf'])>0: + rule.update({ 'rule_from_zone': rule_orig['srcintf'][0] }) # todo: currently only using the first zone + if len(rule_orig['dstintf'])>0: + rule.update({ 'rule_to_zone': rule_orig['dstintf'][0] }) # todo: currently only using the first zone + + rule.update({ 'rule_src_neg': False}) + rule.update({ 'rule_dst_neg': False}) + rule.update({ 'rule_svc_neg': False}) + rule.update({ 'rule_src_refs': resolve_raw_objects(rule['rule_src'], list_delimiter, native_config, 'name', 'uuid', rule_type=rule_table) }, \ + jwt=jwt, import_id=import_id, rule_uid=rule_orig['uuid'], object_type=NETWORK_OBJECT) + rule.update({ 'rule_dst_refs': resolve_raw_objects(rule['rule_dst'], list_delimiter, native_config, 'name', 'uuid', rule_type=rule_table) }, \ + jwt=jwt, import_id=import_id, rule_uid=rule_orig['uuid'], object_type=NETWORK_OBJECT) + # services do not have uids, so using name instead + rule.update({ 'rule_svc_refs': rule['rule_svc'] }) + rule.update({ 'rule_type': 'original' }) + rule.update({ 'rule_installon': localPkgName }) + if 'status' in rule_orig and (rule_orig['status']=='enable' or rule_orig['status']==1): + rule.update({ 'rule_disabled': False }) + else: + rule.update({ 'rule_disabled': True }) + rule.update({ 'rule_implied': False }) + rule.update({ 'rule_time': None }) + rule.update({ 'parent_rule_id': None }) + + nat_rules.append(rule) + add_users_to_rule(rule_orig, rule) + + ############## now adding the xlate rule part ########################## + xlate_rule = dict(rule) # copy the original (match) rule + xlate_rule.update({'rule_src': '', 'rule_dst': '', 'rule_svc': ''}) + xlate_rule['rule_src'] = extend_string_list(xlate_rule['rule_src'], rule_orig, 'orig-addr', list_delimiter, jwt=jwt, import_id=import_id) + xlate_rule['rule_dst'] = 'Original' + + if rule_orig['protocol']==17: + svc_name = 'udp_' + str(rule_orig['nat-port']) + elif rule_orig['protocol']==6: + svc_name = 'tcp_' + str(rule_orig['nat-port']) + else: + svc_name = 'svc_' + str(rule_orig['nat-port']) + # need to create a helper service object and add it to the nat rule, also needs to be added to service list! + # fmgr_service.create_svc_object(name=svc_name, proto=rule_orig['protocol'], port=rule_orig['orig-port'], comment='service created by FWO importer for NAT purposes') + normalized_config_adom['service_objects'].append(create_svc_object(import_id=import_id, name=svc_name, proto=rule_orig['protocol'], port=rule_orig['nat-port'], comment='service created by FWO importer for NAT purposes')) + xlate_rule['rule_svc'] = svc_name - xlate_rule.update({ 'rule_src_refs': resolve_objects(xlate_rule['rule_src'], list_delimiter, native_config, 'name', 'uuid', rule_type=rule_table, jwt=jwt, import_id=import_id ) }) - xlate_rule.update({ 'rule_dst_refs': resolve_objects(xlate_rule['rule_dst'], list_delimiter, native_config, 'name', 'uuid', rule_type=rule_table, jwt=jwt, import_id=import_id ) }) - xlate_rule.update({ 'rule_svc_refs': xlate_rule['rule_svc'] }) # services do not have uids, so using name instead + xlate_rule.update({ 'rule_src_refs': resolve_objects(xlate_rule['rule_src'], list_delimiter, native_config, 'name', 'uuid', rule_type=rule_table, jwt=jwt, import_id=import_id ) }) + xlate_rule.update({ 'rule_dst_refs': resolve_objects(xlate_rule['rule_dst'], list_delimiter, native_config, 'name', 'uuid', rule_type=rule_table, jwt=jwt, import_id=import_id ) }) + xlate_rule.update({ 'rule_svc_refs': xlate_rule['rule_svc'] }) # services do not have uids, so using name instead - xlate_rule.update({ 'rule_type': 'xlate' }) + xlate_rule.update({ 'rule_type': 'xlate' }) - nat_rules.append(xlate_rule) - rule_number += 1 + nat_rules.append(xlate_rule) + rule_number += 1 normalized_config_adom['rules'].extend(nat_rules) diff --git a/roles/importer/files/importer/fortiadom5ff/fwcommon.py b/roles/importer/files/importer/fortiadom5ff/fwcommon.py index 8755abddf7..200780d8ef 100644 --- a/roles/importer/files/importer/fortiadom5ff/fwcommon.py +++ b/roles/importer/files/importer/fortiadom5ff/fwcommon.py @@ -11,7 +11,6 @@ import fmgr_getter from fwo_log import getFwoLogger from fmgr_gw_networking import getInterfacesAndRouting, normalize_network_data -from model_controllers.route_controller import get_ip_of_interface_obj from model_controllers.fwconfigmanagerlist_controller import FwConfigManagerListController from model_controllers.fwconfig_normalized_controller import FwConfigNormalizedController from models.fwconfigmanager import FwConfigManager @@ -194,7 +193,7 @@ def normalize_single_manager_config(native_config: dict[str, Any], native_config current_svc_obj_types) logger.info("completed normalizing service objects for manager: " + native_config.get('domain_name','')) mgm_uid = native_config["management_uid"] - normalize_rulebases(import_state, mgm_uid, native_config, native_config_global, normalized_config_adom, normalized_config_global, + normalize_rulebases(mgm_uid, native_config, native_config_global, normalized_config_adom, normalized_config_global, is_global_loop_iteration) logger.info("completed normalizing rulebases for manager: " + native_config.get('domain_name','')) From cdc31141ba40a0e3bd4825f1c15e179da7f6a542 Mon Sep 17 00:00:00 2001 From: alf-cactus Date: Thu, 23 Oct 2025 16:02:44 +0200 Subject: [PATCH 22/27] forti wip nap --- .../files/importer/fortiadom5ff/fmgr_rule.py | 82 +++---------------- 1 file changed, 12 insertions(+), 70 deletions(-) diff --git a/roles/importer/files/importer/fortiadom5ff/fmgr_rule.py b/roles/importer/files/importer/fortiadom5ff/fmgr_rule.py index 17f0f499e5..4cb44c7732 100644 --- a/roles/importer/files/importer/fortiadom5ff/fmgr_rule.py +++ b/roles/importer/files/importer/fortiadom5ff/fmgr_rule.py @@ -72,14 +72,14 @@ def normalize_rulebases_for_each_link_destination(gateway, mgm_uid, fetched_rule else: normalized_config_adom['policies'].append(normalized_rulebase) - normalize_nat_rulebase(rulebase_link, native_config) + normalize_nat_rulebase(rulebase_link, native_config, normalized_config_adom, normalized_config_global) -def normalize_nat_rulebase(rulebase_link, native_config): +def normalize_nat_rulebase(rulebase_link, native_config, normalized_config_adom, normalized_config_global): if not rulebase_link['is_section']: for nat_type in nat_types: nat_type_string = nat_type + '_' + rulebase_link['to_rulebase_uid'] nat_rulebase = get_native_nat_rulebase(native_config, nat_type_string) - parse_nat_rulebase(nat_rulebase) + parse_nat_rulebase(nat_rulebase, normalized_config_adom, normalized_config_global) def get_native_nat_rulebase(native_config, nat_type_string): logger = getFwoLogger() @@ -452,11 +452,15 @@ def get_nat_policy(sid, fm_api_url, native_config, adom_device_vdom_policy_packa # delete_v: ab hier kann sehr viel weg, ich lasses vorerst zB für die nat # pure nat rules -def parse_nat_rulebase(nat_rulebase): #(nat_rulebase, normalized_config_adom, import_id, jwt=None): + + +def parse_nat_rulebase(nat_rulebase, normalized_config_adom, normalized_config_global): #(nat_rulebase, normalized_config_adom, import_id, jwt=None): nat_rules = [] rule_number = 0 for rule_orig in nat_rulebase: + rule_src_list, rule_src_refs_list = rule_parse_addresses(rule_orig, 'src', normalized_config_adom, normalized_config_global) + rule_dst_list, rule_dst_refs_list = rule_parse_addresses(rule_orig, 'dst', normalized_config_adom, normalized_config_global) rule_normalized = RuleNormalized( rule_num=rule_number, @@ -475,11 +479,11 @@ def parse_nat_rulebase(nat_rulebase): #(nat_rulebase, normalized_config_adom, im rule_track=rule_track, rule_installon=rule_installon, rule_time='', # Time-based rules not commonly used in basic Fortinet configs - rule_name=native_rule.get('name'), - rule_uid=native_rule.get('uuid'), - rule_custom_fields=str(native_rule.get('meta fields', {})), + rule_name=rule_orig.get('name', ''), + rule_uid=rule_orig.get('uuid'), + rule_custom_fields=str({}), rule_implied=False, - rule_type=RuleType.ACCESS, + rule_type=RuleType.NAT, last_change_admin=native_rule.get('_last-modified-by', ''), parent_rule_uid=None, last_hit=last_hit, @@ -578,37 +582,6 @@ def parse_nat_rulebase(nat_rulebase): #(nat_rulebase, normalized_config_adom, im rule_number += 1 normalized_config_adom['rules'].extend(nat_rules) - -def insert_header(rules, import_id, header_text, rulebase_name, rule_uid, rule_number, src_refs, dst_refs): - rule = { - "control_id": import_id, - "rule_head_text": header_text, - "rulebase_name": rulebase_name, - "rule_ruleid": None, - "rule_uid": rule_uid + rulebase_name, - "rule_num": rule_number, - "rule_disabled": False, - "rule_src": "all", - "rule_dst": "all", - "rule_svc": "ALL", - "rule_src_neg": False, - "rule_dst_neg": False, - "rule_svc_neg": False, - "rule_src_refs": src_refs, - "rule_dst_refs": dst_refs, - "rule_svc_refs": "ALL", - "rule_action": "Accept", - "rule_track": "None", - "rule_installon": None, - "rule_time": None, - "rule_type": "access", - "parent_rule_id": None, - "rule_implied": False, - "rule_comment": None - } - rules.append(rule) - - def create_xlate_rule(rule): xlate_rule = copy.deepcopy(rule) rule['rule_type'] = 'combined' @@ -722,37 +695,6 @@ def handle_combined_nat_rule(rule, rule_orig, config2import, nat_rule_number, im return xlate_rule - -def insert_headers(rule_table, first_v6, first_v4, full_config, rules, import_id, localPkgName,src_ref_all,dst_ref_all,rule_number): - if rule_table in rule_access_scope_v6 and first_v6: - insert_header(rules, import_id, "IPv6 rules", localPkgName, "IPv6HeaderText", rule_number, src_ref_all, dst_ref_all) - rule_number += 1 - first_v6 = False - elif rule_table in rule_access_scope_v4 and first_v4: - insert_header(rules, import_id, "IPv4 rules", localPkgName, "IPv4HeaderText", rule_number, src_ref_all, dst_ref_all) - rule_number += 1 - first_v4 = False - if rule_table == 'rules_adom_v4' and len(full_config['rules_adom_v4'][localPkgName])>0: - insert_header(rules, import_id, "Adom Rules IPv4", localPkgName, "IPv4AdomRules", rule_number, src_ref_all, dst_ref_all) - rule_number += 1 - elif rule_table == 'rules_adom_v6' and len(full_config['rules_adom_v6'][localPkgName])>0: - insert_header(rules, import_id, "Adom Rules IPv6", localPkgName, "IPv6AdomRules", rule_number, src_ref_all, dst_ref_all) - rule_number += 1 - elif rule_table == 'rules_global_header_v4' and len(full_config['rules_global_header_v4'][localPkgName])>0: - insert_header(rules, import_id, "Global Header Rules IPv4", localPkgName, "IPv4GlobalHeaderRules", rule_number, src_ref_all, dst_ref_all) - rule_number += 1 - elif rule_table == 'rules_global_header_v6' and len(full_config['rules_global_header_v6'][localPkgName])>0: - insert_header(rules, import_id, "Global Header Rules IPv6", localPkgName, "IPv6GlobalHeaderRules", rule_number, src_ref_all, dst_ref_all) - rule_number += 1 - elif rule_table == 'rules_global_footer_v4' and len(full_config['rules_global_footer_v4'][localPkgName])>0: - insert_header(rules, import_id, "Global Footer Rules IPv4", localPkgName, "IPv4GlobalFooterRules", rule_number, src_ref_all, dst_ref_all) - rule_number += 1 - elif rule_table == 'rules_global_footer_v6' and len(full_config['rules_global_footer_v6'][localPkgName])>0: - insert_header(rules, import_id, "Global Footer Rules IPv6", localPkgName, "IPv6GlobalFooterRules", rule_number, src_ref_all, dst_ref_all) - rule_number += 1 - return rule_number, first_v4, first_v6 - - def extract_nat_objects(nwobj_list, all_nwobjects): nat_obj_list = [] for obj in nwobj_list: From b3899075f601ebcfba7db2df0bd6136a98290743 Mon Sep 17 00:00:00 2001 From: alf-cactus Date: Fri, 24 Oct 2025 14:38:43 +0200 Subject: [PATCH 23/27] nat wip --- .../files/importer/fortiadom5ff/fmgr_rule.py | 27 ++++++++++++------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/roles/importer/files/importer/fortiadom5ff/fmgr_rule.py b/roles/importer/files/importer/fortiadom5ff/fmgr_rule.py index 4cb44c7732..06d7cd62f3 100644 --- a/roles/importer/files/importer/fortiadom5ff/fmgr_rule.py +++ b/roles/importer/files/importer/fortiadom5ff/fmgr_rule.py @@ -119,8 +119,8 @@ def add_implicit_deny_rule(rule_num, normalized_config_adom, normalized_config_g 'service': ['ALL'], 'srcintf': ['any'], 'dstintf': ['any']} - rule_src_list, rule_src_refs_list = rule_parse_addresses(deny_rule, 'src', normalized_config_adom, normalized_config_global) - rule_dst_list, rule_dst_refs_list = rule_parse_addresses(deny_rule, 'dst', normalized_config_adom, normalized_config_global) + rule_src_list, rule_src_refs_list = rule_parse_addresses(deny_rule, 'src', normalized_config_adom, normalized_config_global, False) + rule_dst_list, rule_dst_refs_list = rule_parse_addresses(deny_rule, 'dst', normalized_config_adom, normalized_config_global, False) rule_svc_list, rule_svc_refs_list = rule_parse_service(deny_rule) rule_src_zones = find_zones_in_normalized_config( deny_rule.get('srcintf', []), normalized_config_adom, normalized_config_global) @@ -169,8 +169,8 @@ def parse_single_rule(normalized_config_adom, normalized_config_global, native_r rule_track = rule_parse_tracking_info(native_rule) - rule_src_list, rule_src_refs_list = rule_parse_addresses(native_rule, 'src', normalized_config_adom, normalized_config_global) - rule_dst_list, rule_dst_refs_list = rule_parse_addresses(native_rule, 'dst', normalized_config_adom, normalized_config_global) + rule_src_list, rule_src_refs_list = rule_parse_addresses(native_rule, 'src', normalized_config_adom, normalized_config_global, False) + rule_dst_list, rule_dst_refs_list = rule_parse_addresses(native_rule, 'dst', normalized_config_adom, normalized_config_global, False) rule_svc_list, rule_svc_refs_list = rule_parse_service(native_rule) @@ -253,13 +253,16 @@ def rule_parse_service(native_rule): return rule_svc_list, rule_svc_refs_list -def rule_parse_addresses(native_rule, target, normalized_config_adom, normalized_config_global): +def rule_parse_addresses(native_rule, target, normalized_config_adom, normalized_config_global, is_nat): if target not in ['src', 'dst']: raise FwoImporterErrorInconsistencies(f"target '{target}' must either be src or dst.") addr_list = [] addr_ref_list = [] - build_addr_list(native_rule, True, target, normalized_config_adom, normalized_config_global, addr_list, addr_ref_list) - build_addr_list(native_rule, False, target, normalized_config_adom, normalized_config_global, addr_list, addr_ref_list) + if not is_nat: + build_addr_list(native_rule, True, target, normalized_config_adom, normalized_config_global, addr_list, addr_ref_list) + build_addr_list(native_rule, False, target, normalized_config_adom, normalized_config_global, addr_list, addr_ref_list) + else: + build_nat_addr_list(native_rule, target, normalized_config_adom, normalized_config_global, addr_list, addr_ref_list) return addr_list, addr_ref_list def build_addr_list(native_rule, is_v4, target, normalized_config_adom, normalized_config_global, addr_list, addr_ref_list): @@ -280,6 +283,12 @@ def build_addr_list(native_rule, is_v4, target, normalized_config_adom, normaliz addr_list.append(addr) addr_ref_list.append(find_addr_ref(addr, is_v4, normalized_config_adom, normalized_config_global)) +def build_nat_addr_list(native_rule, target, normalized_config_adom, normalized_config_global, addr_list, addr_ref_list): + if is_v4 and target == 'src': + for addr in native_rule.get('orig-addr', []): + addr_list.append(addr) + addr_ref_list.append(find_addr_ref(addr, is_v4, normalized_config_adom, normalized_config_global)) + def find_addr_ref(addr, is_v4, normalized_config_adom, normalized_config_global): for nw_obj in normalized_config_adom['network_objects'] + normalized_config_global.get('network_objects', []): if addr == nw_obj['obj_name']: @@ -459,8 +468,8 @@ def parse_nat_rulebase(nat_rulebase, normalized_config_adom, normalized_config_g rule_number = 0 for rule_orig in nat_rulebase: - rule_src_list, rule_src_refs_list = rule_parse_addresses(rule_orig, 'src', normalized_config_adom, normalized_config_global) - rule_dst_list, rule_dst_refs_list = rule_parse_addresses(rule_orig, 'dst', normalized_config_adom, normalized_config_global) + rule_src_list, rule_src_refs_list = rule_parse_addresses(rule_orig, 'src', normalized_config_adom, normalized_config_global, True) + rule_dst_list, rule_dst_refs_list = rule_parse_addresses(rule_orig, 'dst', normalized_config_adom, normalized_config_global, True) rule_normalized = RuleNormalized( rule_num=rule_number, From eb9960009019f056cd3c210900988b9b3be632ca Mon Sep 17 00:00:00 2001 From: alf-cactus Date: Fri, 24 Oct 2025 15:37:13 +0200 Subject: [PATCH 24/27] nat wip --- .../importer/files/importer/fortiadom5ff/fmgr_rule.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/roles/importer/files/importer/fortiadom5ff/fmgr_rule.py b/roles/importer/files/importer/fortiadom5ff/fmgr_rule.py index 06d7cd62f3..d1168b4844 100644 --- a/roles/importer/files/importer/fortiadom5ff/fmgr_rule.py +++ b/roles/importer/files/importer/fortiadom5ff/fmgr_rule.py @@ -219,7 +219,7 @@ def parse_single_rule(normalized_config_adom, normalized_config_global, native_r # Add the rule to the rulebase rulebase.Rules[rule_normalized.rule_uid] = rule_normalized - # TODO: handle NAT + # TODO: handle combined NAT, see handle_combined_nat_rule return rule_num + 1 @@ -284,10 +284,15 @@ def build_addr_list(native_rule, is_v4, target, normalized_config_adom, normaliz addr_ref_list.append(find_addr_ref(addr, is_v4, normalized_config_adom, normalized_config_global)) def build_nat_addr_list(native_rule, target, normalized_config_adom, normalized_config_global, addr_list, addr_ref_list): - if is_v4 and target == 'src': + # so far only ip v4 expected + if target == 'src': for addr in native_rule.get('orig-addr', []): addr_list.append(addr) - addr_ref_list.append(find_addr_ref(addr, is_v4, normalized_config_adom, normalized_config_global)) + addr_ref_list.append(find_addr_ref(addr, True, normalized_config_adom, normalized_config_global)) + if target == 'dst': + for addr in native_rule.get('dst-addr', []): + addr_list.append(addr) + addr_ref_list.append(find_addr_ref(addr, True, normalized_config_adom, normalized_config_global)) def find_addr_ref(addr, is_v4, normalized_config_adom, normalized_config_global): for nw_obj in normalized_config_adom['network_objects'] + normalized_config_global.get('network_objects', []): From 3845647fa8cd1afab5e27bb1bd122f45f920f71d Mon Sep 17 00:00:00 2001 From: alf-cactus Date: Tue, 28 Oct 2025 14:58:36 +0100 Subject: [PATCH 25/27] nat wip --- roles/importer/files/importer/fortiadom5ff/fmgr_rule.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/roles/importer/files/importer/fortiadom5ff/fmgr_rule.py b/roles/importer/files/importer/fortiadom5ff/fmgr_rule.py index a404f8245a..aba10fd39d 100644 --- a/roles/importer/files/importer/fortiadom5ff/fmgr_rule.py +++ b/roles/importer/files/importer/fortiadom5ff/fmgr_rule.py @@ -490,8 +490,8 @@ def parse_nat_rulebase(nat_rulebase, normalized_config_adom, normalized_config_g rule_svc_neg=False, rule_svc=list_delimiter.join(rule_svc_list), rule_svc_refs=list_delimiter.join(rule_svc_refs_list), - rule_action=rule_action, - rule_track=rule_track, + rule_action=RuleAction.DROP, + rule_track=RuleTrack.NONE, rule_installon=rule_installon, rule_time='', # Time-based rules not commonly used in basic Fortinet configs rule_name=rule_orig.get('name', ''), From 574ab523aeccb4e0295c267a98ef00ecae554c70 Mon Sep 17 00:00:00 2001 From: alf-cactus Date: Thu, 30 Oct 2025 08:42:46 +0100 Subject: [PATCH 26/27] ready for testing --- .../files/importer/fortiadom5ff/fmgr_rule.py | 24 +++++++++---------- .../importer/fortiadom5ff/fmgr_service.py | 4 ++-- .../files/importer/fortiadom5ff/fwcommon.py | 12 +++++----- 3 files changed, 19 insertions(+), 21 deletions(-) diff --git a/roles/importer/files/importer/fortiadom5ff/fmgr_rule.py b/roles/importer/files/importer/fortiadom5ff/fmgr_rule.py index aba10fd39d..bf0cf8a362 100644 --- a/roles/importer/files/importer/fortiadom5ff/fmgr_rule.py +++ b/roles/importer/files/importer/fortiadom5ff/fmgr_rule.py @@ -1,5 +1,4 @@ import copy -import jsonpickle import ipaddress from time import strftime, localtime from fwo_const import list_delimiter, nat_postfix, dummy_ip @@ -32,10 +31,10 @@ def normalize_rulebases( normalized_config_global: dict, is_global_loop_iteration: bool ) -> None: + normalized_config_adom['policies'] = [] - fetched_rulebase_uids: list = [] - if normalized_config_global is not None: + if normalized_config_global != {}: for normalized_rulebase_global in normalized_config_global.get('policies', []): fetched_rulebase_uids.append(normalized_rulebase_global.uid) for gateway in native_config['gateways']: @@ -44,9 +43,6 @@ def normalize_rulebases( is_global_loop_iteration, normalized_config_adom, normalized_config_global) - # todo: parse nat rulebase here - - def normalize_rulebases_for_each_link_destination(gateway, mgm_uid, fetched_rulebase_uids, native_config, native_config_global, is_global_loop_iteration, normalized_config_adom, normalized_config_global): logger = getFwoLogger() @@ -55,7 +51,7 @@ def normalize_rulebases_for_each_link_destination(gateway, mgm_uid, fetched_rule rulebase_to_parse = find_rulebase_to_parse(native_config['rulebases'], rulebase_link['to_rulebase_uid']) # search in global rulebase found_rulebase_in_global = False - if rulebase_to_parse == {} and not is_global_loop_iteration and native_config_global is not None: + if rulebase_to_parse == {} and not is_global_loop_iteration and native_config_global != {}: rulebase_to_parse = find_rulebase_to_parse( native_config_global['rulebases'], rulebase_link['to_rulebase_uid'] ) @@ -73,14 +69,15 @@ def normalize_rulebases_for_each_link_destination(gateway, mgm_uid, fetched_rule else: normalized_config_adom['policies'].append(normalized_rulebase) - normalize_nat_rulebase(rulebase_link, native_config, normalized_config_adom, normalized_config_global) + # normalizing nat rulebases is work in progress + #normalize_nat_rulebase(rulebase_link, native_config, normalized_config_adom, normalized_config_global) def normalize_nat_rulebase(rulebase_link, native_config, normalized_config_adom, normalized_config_global): if not rulebase_link['is_section']: for nat_type in nat_types: nat_type_string = nat_type + '_' + rulebase_link['to_rulebase_uid'] nat_rulebase = get_native_nat_rulebase(native_config, nat_type_string) - parse_nat_rulebase(nat_rulebase, normalized_config_adom, normalized_config_global) + parse_nat_rulebase(nat_rulebase, nat_type_string, normalized_config_adom, normalized_config_global) def get_native_nat_rulebase(native_config, nat_type_string): logger = getFwoLogger() @@ -468,8 +465,9 @@ def get_nat_policy(sid, fm_api_url, native_config, adom_device_vdom_policy_packa # delete_v: ab hier kann sehr viel weg, ich lasses vorerst zB für die nat # pure nat rules - -def parse_nat_rulebase(nat_rulebase, normalized_config_adom, normalized_config_global): #(nat_rulebase, normalized_config_adom, import_id, jwt=None): +def parse_nat_rulebase(nat_rulebase, nat_type_string, normalized_config_adom, normalized_config_global): + # this function is not called until it is ready + return nat_rules = [] rule_number = 0 for rule_orig in nat_rulebase: @@ -492,7 +490,7 @@ def parse_nat_rulebase(nat_rulebase, normalized_config_adom, normalized_config_g rule_svc_refs=list_delimiter.join(rule_svc_refs_list), rule_action=RuleAction.DROP, rule_track=RuleTrack.NONE, - rule_installon=rule_installon, + rule_installon=nat_type_string, rule_time='', # Time-based rules not commonly used in basic Fortinet configs rule_name=rule_orig.get('name', ''), rule_uid=rule_orig.get('uuid'), @@ -641,7 +639,7 @@ def handle_combined_nat_rule(rule, rule_orig, config2import, nat_rule_number, im interface_name = matching_route.interface hideInterface=interface_name if hideInterface is None: - logger.warning('src nat behind interface: found route with undefined interface ' + str(jsonpickle.dumps(matching_route, unpicklable=True))) + logger.warning('src nat behind interface: found route with undefined interface ') #+ str(jsonpickle.dumps(matching_route, unpicklable=True))) if destination_interface_ip is None: logger.warning('src nat behind interface: found no matching interface IP in rule with UID ' + rule['rule_uid'] + ', dest_ip: ' + destination_ip) diff --git a/roles/importer/files/importer/fortiadom5ff/fmgr_service.py b/roles/importer/files/importer/fortiadom5ff/fmgr_service.py index c54f75c840..8cb4853a00 100644 --- a/roles/importer/files/importer/fortiadom5ff/fmgr_service.py +++ b/roles/importer/files/importer/fortiadom5ff/fmgr_service.py @@ -119,7 +119,7 @@ def check_split(obj_orig) -> bool: return (count > 1) -def extractPorts(port_ranges) -> tuple[list[Any], list[Any]]: +def extractPorts(port_ranges) -> 'tuple[list[Any], list[Any]]': ports = [] port_ends = [] if port_ranges is not None and len(port_ranges) > 0: @@ -150,7 +150,7 @@ def extractPorts(port_ranges) -> tuple[list[Any], list[Any]]: return ports, port_ends -def create_svc_object(name, proto, color, port, comment) -> dict[str, Any]: +def create_svc_object(name, proto, color, port, comment) -> 'dict[str, Any]': return { 'svc_name': name, 'svc_typ': 'simple', diff --git a/roles/importer/files/importer/fortiadom5ff/fwcommon.py b/roles/importer/files/importer/fortiadom5ff/fwcommon.py index 200780d8ef..48a589570d 100644 --- a/roles/importer/files/importer/fortiadom5ff/fwcommon.py +++ b/roles/importer/files/importer/fortiadom5ff/fwcommon.py @@ -1,8 +1,5 @@ -from curses import raw from typing import Any - -import json import fwo_const from copy import deepcopy from model_controllers.import_state_controller import ImportStateController @@ -17,7 +14,7 @@ from model_controllers.management_controller import ManagementController from fmgr_network import normalize_network_objects from fmgr_service import normalize_service_objects -from fmgr_rule import normalize_rulebases, get_access_policy, get_nat_policy, normalize_nat_rulebases +from fmgr_rule import normalize_rulebases, get_access_policy, get_nat_policy from fmgr_consts import nw_obj_types, svc_obj_types, user_obj_types from fwo_base import ConfigAction from fmgr_zone import get_zones, normalize_zones @@ -108,7 +105,7 @@ def get_arbitrary_vdom(adom_device_vdom_structure): return {'adom': adom, 'device': device, 'vdom': vdom} -def normalize_config(import_state, native_config: dict[str,Any]) -> FwConfigManagerListController: +def normalize_config(import_state, native_config: 'dict[str,Any]') -> FwConfigManagerListController: manager_list = FwConfigManagerListController() @@ -117,6 +114,9 @@ def normalize_config(import_state, native_config: dict[str,Any]) -> FwConfigMana rewrite_native_config_obj_type_as_key(native_config) # for easier accessability of objects in normalization process + native_config_global = {} + normalized_config_global = {} + for native_conf in native_config['domains']: normalized_config_adom = deepcopy(fwo_const.emptyNormalizedFwConfigJsonDict) is_global_loop_iteration = False @@ -169,7 +169,7 @@ def rewrite_native_config_obj_type_as_key(native_config): domain['objects'] = obj_dict -def normalize_single_manager_config(native_config: dict[str, Any], native_config_global: dict[str, Any], normalized_config_adom: dict, +def normalize_single_manager_config(native_config: 'dict[str, Any]', native_config_global: 'dict[str, Any]', normalized_config_adom: dict, normalized_config_global: dict, import_state: ImportStateController, is_global_loop_iteration: bool): From 1a5830f8dc280ac2113b151f59233e8d58f6793e Mon Sep 17 00:00:00 2001 From: alf-cactus Date: Thu, 30 Oct 2025 09:13:31 +0100 Subject: [PATCH 27/27] refactor --- .../files/importer/fortiadom5ff/fmgr_network.py | 2 +- .../files/importer/fortiadom5ff/fmgr_rule.py | 14 ++++++++------ .../files/importer/fortiadom5ff/fmgr_service.py | 8 +++----- .../files/importer/fortiadom5ff/fwcommon.py | 10 ++-------- 4 files changed, 14 insertions(+), 20 deletions(-) diff --git a/roles/importer/files/importer/fortiadom5ff/fmgr_network.py b/roles/importer/files/importer/fortiadom5ff/fmgr_network.py index 8ad980b766..cdf8d5feaf 100644 --- a/roles/importer/files/importer/fortiadom5ff/fmgr_network.py +++ b/roles/importer/files/importer/fortiadom5ff/fmgr_network.py @@ -9,7 +9,7 @@ from fwo_exceptions import FwoImporterErrorInconsistencies -def normalize_network_objects(native_config, native_config_global, normalized_config_adom, normalized_config_global, nw_obj_types): +def normalize_network_objects(native_config, normalized_config_adom, normalized_config_global, nw_obj_types): nw_objects = [] if 'objects' not in native_config: diff --git a/roles/importer/files/importer/fortiadom5ff/fmgr_rule.py b/roles/importer/files/importer/fortiadom5ff/fmgr_rule.py index bf0cf8a362..44e6be11eb 100644 --- a/roles/importer/files/importer/fortiadom5ff/fmgr_rule.py +++ b/roles/importer/files/importer/fortiadom5ff/fmgr_rule.py @@ -16,6 +16,8 @@ NETWORK_OBJECT='network_object' STRING_PKG = '/pkg/' +STRING_PM_CONFIG_GLOBAL_PKG = '/pm/config/global/pkg/' +STRING_PM_CONFIG_ADOM = '/pm/config/adom/' rule_access_scope_v4 = ['rules_global_header_v4', 'rules_adom_v4', 'rules_global_footer_v4'] rule_access_scope_v6 = ['rules_global_header_v6', 'rules_adom_v6', 'rules_global_footer_v6'] rule_access_scope = rule_access_scope_v6 + rule_access_scope_v4 @@ -357,7 +359,7 @@ def get_and_link_global_rulebase(header_or_footer, previous_rulebase, global_pkg fmgr_getter.update_config_with_fortinet_api_call( native_config_global['rulebases'], sid, fm_api_url, - '/pm/config/global/pkg/' + global_pkg_name + '/global/' + header_or_footer + '/policy', + STRING_PM_CONFIG_GLOBAL_PKG + global_pkg_name + '/global/' + header_or_footer + '/policy', rulebase_type_prefix + '_v4_' + global_pkg_name, options=options, limit=limit) if not is_rulebase_already_fetched(native_config_global['rulebases'], rulebase_type_prefix + '_v6_' + global_pkg_name): @@ -365,7 +367,7 @@ def get_and_link_global_rulebase(header_or_footer, previous_rulebase, global_pkg fmgr_getter.update_config_with_fortinet_api_call( native_config_global['rulebases'], sid, fm_api_url, - '/pm/config/global/pkg/' + global_pkg_name + '/global/' + header_or_footer + '/policy6', + STRING_PM_CONFIG_GLOBAL_PKG + global_pkg_name + '/global/' + header_or_footer + '/policy6', rulebase_type_prefix + '_v6_' + global_pkg_name, limit=limit) previous_rulebase = link_rulebase(link_list, native_config_global['rulebases'], global_pkg_name, rulebase_type_prefix, previous_rulebase, True) @@ -376,14 +378,14 @@ def get_and_link_local_rulebase(rulebase_type_prefix, previous_rulebase, adom_na fmgr_getter.update_config_with_fortinet_api_call( native_config_adom['rulebases'], sid, fm_api_url, - '/pm/config/adom/' + adom_name + '/pkg/' + local_pkg_name + '/firewall/policy', + STRING_PM_CONFIG_ADOM + adom_name + STRING_PKG + local_pkg_name + '/firewall/policy', rulebase_type_prefix + '_v4_' + local_pkg_name, options=options, limit=limit) if not is_rulebase_already_fetched(native_config_adom['rulebases'], rulebase_type_prefix + '_v6_' + local_pkg_name): fmgr_getter.update_config_with_fortinet_api_call( native_config_adom['rulebases'], sid, fm_api_url, - '/pm/config/adom/' + adom_name + '/pkg/' + local_pkg_name + '/firewall/policy6', + STRING_PM_CONFIG_ADOM + adom_name + STRING_PKG + local_pkg_name + '/firewall/policy6', rulebase_type_prefix + '_v6_' + local_pkg_name, limit=limit) previous_rulebase = link_rulebase(link_list, native_config_adom['rulebases'], local_pkg_name, rulebase_type_prefix, previous_rulebase, False) @@ -453,13 +455,13 @@ def get_nat_policy(sid, fm_api_url, native_config, adom_device_vdom_policy_packa for nat_type in nat_types: fmgr_getter.update_config_with_fortinet_api_call( native_config['nat_rulebases'], sid, fm_api_url, - '/pm/config/global/pkg/' + global_pkg_name + '/' + nat_type, + STRING_PM_CONFIG_GLOBAL_PKG + global_pkg_name + '/' + nat_type, nat_type + '_global_' + global_pkg_name, limit=limit) else: for nat_type in nat_types: fmgr_getter.update_config_with_fortinet_api_call( native_config['nat_rulebases'], sid, fm_api_url, - '/pm/config/adom/' + adom_name + '/pkg/' + local_pkg_name + '/' + nat_type, + STRING_PM_CONFIG_ADOM + adom_name + STRING_PKG + local_pkg_name + '/' + nat_type, nat_type + '_adom_' + adom_name + '_' + local_pkg_name, limit=limit) # delete_v: ab hier kann sehr viel weg, ich lasses vorerst zB für die nat diff --git a/roles/importer/files/importer/fortiadom5ff/fmgr_service.py b/roles/importer/files/importer/fortiadom5ff/fmgr_service.py index 8cb4853a00..7a6fcf567a 100644 --- a/roles/importer/files/importer/fortiadom5ff/fmgr_service.py +++ b/roles/importer/files/importer/fortiadom5ff/fmgr_service.py @@ -4,10 +4,8 @@ from fwo_log import getFwoLogger from typing import Any -def normalize_service_objects(import_state: ImportStateController, native_config, native_config_global, normalized_config_adom, - normalized_config_global, svc_obj_types): +def normalize_service_objects(native_config, normalized_config_adom, svc_obj_types): svc_objects = [] - logger = getFwoLogger() if 'objects' not in native_config: return # no objects to normalize @@ -119,7 +117,7 @@ def check_split(obj_orig) -> bool: return (count > 1) -def extractPorts(port_ranges) -> 'tuple[list[Any], list[Any]]': +def extract_ports(port_ranges) -> 'tuple[list[Any], list[Any]]': ports = [] port_ends = [] if port_ranges is not None and len(port_ranges) > 0: @@ -179,7 +177,7 @@ def add_object(svc_objects, type, name, color, proto, port_ranges, member_names, }]) else: range_names = '' - ports, port_ends = extractPorts(port_ranges) + ports, port_ends = extract_ports(port_ranges) split = (len(ports) > 1) for index, port in enumerate(ports): port_end = port_ends[index] diff --git a/roles/importer/files/importer/fortiadom5ff/fwcommon.py b/roles/importer/files/importer/fortiadom5ff/fwcommon.py index 48a589570d..e642fd62bc 100644 --- a/roles/importer/files/importer/fortiadom5ff/fwcommon.py +++ b/roles/importer/files/importer/fortiadom5ff/fwcommon.py @@ -186,22 +186,16 @@ def normalize_single_manager_config(native_config: 'dict[str, Any]', native_conf logger = getFwoLogger() normalize_zones(native_config, normalized_config_adom, is_global_loop_iteration) logger.info("completed normalizing zones for manager: " + native_config.get('domain_name','')) - normalize_network_objects(native_config, native_config_global, normalized_config_adom, normalized_config_global, + normalize_network_objects(native_config, normalized_config_adom, normalized_config_global, current_nw_obj_types) logger.info("completed normalizing network objects for manager: " + native_config.get('domain_name','')) - normalize_service_objects(import_state, native_config, native_config_global, normalized_config_adom, normalized_config_global, - current_svc_obj_types) + normalize_service_objects(native_config, normalized_config_adom, current_svc_obj_types) logger.info("completed normalizing service objects for manager: " + native_config.get('domain_name','')) mgm_uid = native_config["management_uid"] normalize_rulebases(mgm_uid, native_config, native_config_global, normalized_config_adom, normalized_config_global, is_global_loop_iteration) logger.info("completed normalizing rulebases for manager: " + native_config.get('domain_name','')) - #normalize_nat_rulebases(import_state, mgm_uid, native_config, native_config_global, normalized_config_adom, normalized_config_global, - # is_global_loop_iteration) - #normalize_nat_rulebases(native_config, normalized_config_adom, import_id, jwt=None) - #logger.info("completed normalizing nat rulebases for manager: " + native_config.get('domain_name','')) - normalize_gateways(native_config, normalized_config_adom)