This is a small python script that will allow you to convert pfSense configurations in a version agnostic xml method that will allow you to search and replace for substrings in strings and base64. Useful when having to update strings inside plugins like filer!

run as python3 migrate-config.py /path/to/config/or/config_template

GitHub https://github.com/celesrenata/pfConfig

#!/usr/bin/python3
import base64
import json
import sys
import xml.etree.ElementTree as ET


def iterate_search_and_replace(config_dict, xml_tree):
    # Create iterable loop for search and replace
    s_and_r = {}
    for group in config_dict:
        if 'example' == group:
            continue
        s_and_r.update({group: {'tag': '', 'xml_string': '', 'plugins': [], 'search': {}, 'replace': {}}})
        for key, value in config_dict[group].items():
            task = None
            if '_search' in key.lower():
                name = key.split("_search")[0]
                task = 'search'
            if '_replace' in key.lower():
                name = key.split("_replace")[0]
                task = 'replace'
            if task is not None:
                s_and_r[group][task][name] = value
            if 'tag' == key:
                s_and_r[group]['tag'] = value
            if 'xml_string' == key:
                s_and_r[group]['xml_string'] = value
            if 'plugins' == key:
                for plugin in config_dict[group]['plugins']:
                    s_and_r[group]['plugins'].append(plugin)

    for group in s_and_r.keys():
        xml_object = tree.findall(s_and_r[group]['xml_string'])
        tag = s_and_r[group]['tag']
        for xml_value in xml_object:
            if tag is not None:
                if xml_value.tag != tag:
                    continue
            for search_key in s_and_r[group]['search'].keys():
                search_value = s_and_r[group]['search'][search_key]
                filtered_output = plugin_filter(xml_value.text, s_and_r[group]['plugins'], 'decode')
                if search_value in filtered_output:
                    search_output = filtered_output.replace(search_value, s_and_r[group]['replace'][search_key])
                    xml_value.text = plugin_filter(search_output, s_and_r[group]['plugins'], 'encode')
    return xml_tree


def plugin_filter(text, plugins, direction):
    # This parses the text through the plugins in ORDER
    for plugin in plugins:
        if plugin is None:
            return text
        if 'base64' in plugin:
            return base64_parse(text, direction)


def base64_parse(text, direction):
    message = text.encode('utf-8')
    if 'encode' == direction:
        new_base64_bytes = base64.b64encode(message)
        return new_base64_bytes.decode('utf-8')
    if 'decode' == direction:
        old_message_bytes = base64.b64decode(message)
        return old_message_bytes.decode('utf-8')


print("use as python3 migrate-config.py FULL-path-to-virtualhome-config-xml")
print("Will output to the same filename with the appended extension '.new'")

migration_config = {}
with open("../resources/network-config.json", "r") as json_file:
    migration_config = json.load(json_file)

# Update Core Values
tree = ET.parse(sys.argv[1])
tree = iterate_search_and_replace(migration_config['search_and_replace'], tree)
tree.write(sys.argv[1] + '.new')
print("check " + sys.argv[1] + ".new for your updated config!")
{
  "search_and_replace": {
    "example": {
      "tag": "tag signifies the xml subgroup used for where data is ultimately being modified",
      "plugins": "currently we only support base64 and null as options",
      "xml_string": "What values you want to modify, such as dhcp options, wireguard tunnels, filer file data, etc",
      "some_string_search": "you can add additional search and replace parameters",
      "some_string_replace": "as long as you leave it in this format with the _search and the _replace as the final substring"
    },
    "system_hostname": {
      "tag": null,
      "plugins": [
        null
      ],
      "xml_string": "./system/hostname",
      "hostname_search": "HOSTNAME",
      "hostname_replace": "varHOSTNAME"
    },
    "system_domain": {
      "tag": null,
      "plugins": [
        null
      ],
      "xml_string": "./system/domain",
      "hostname_search": "DOMAIN",
      "hostname_replace": "varDOMAIN"
    },
    "interface_lan_ipaddress": {
      "tag": null,
      "plugins": [
        null
      ],
      "xml_string": "./interfaces/lan/ipaddr",
      "hostname_search": "HOSTIPADDRESS",
      "hostname_replace": "varHOSTIPADDRESS"
    },
    "firewall_rules": {
      "tag": null,
      "plugins": [
        null
      ],
      "xml_string": "./nat/outbound/rule/source/network",
      "source_network_search": "CONFIG_LANCIDR",
      "source_network_replace": "varLANCIDR"
    },
    "filer_data": {
      "tag": "filedata",
      "plugins": [
        "base64"
      ],
      "xml_string": "./installedpackages/filer/config",
      "gateway_search": "192.168.42.1",
      "gateway_replace": "varHOSTIPADDRESS"
    },
    "user_data": {
      "tag": null,
      "plugins": [
        null
      ],
      "xml_string": "./system/user/name",
      "username_search": "CONFIG_USER",
      "username_replace": "varVPNUSER"
    },
    "interface_wan": {
      "tag": null,
      "plugins": [
        null
      ],
      "xml_string": "./interfaces/wan/if",
      "interface_search": "vtlan0",
      "interface_replace": "varWANINTERFACE"
    },
    "interface_Lan": {
      "tag": null,
      "plugins": [
        null
      ],
      "xml_string": "./interfaces/lan/if",
      "interface_search": "vtlan0",
      "interface_replace": "varLANINTERFACE"
    },
    "wireguard_tunnel_private_key": {
      "tag": null,
      "plugins": [
        null
        ],
      "xml_string": "./installedpackages/wireguard/tunnels/item/privatekey",
      "nord_private_key_search": "EBsUrudEkaZKyXozwmI+gLbKGh+/wJAgbEvKUjJa0lM=",
      "nord_private_key_replace": "NORD_PRIVATE_KEY",
      "surf_private_key_search": "0D6BCbkcmX1YGlz0HLosrl30+IFclCrvePyTVMpSSWI=",
      "surf_private_key_replace": "SURF_PRIVATE_KEY",
      "mull_private_key_search": "6AfufR81SNPqYrkJwrAn7OjTzHV+rypIDjGgOuYgyGc=",
      "mull_private_key_replace": "MULL_PRIVATE_KEY"
    },
    "wireguard_tunnel_mtu": {
      "tag": null,
      "plugins": [
        null
      ],
      "xml_string": "./installedpackages/wireguard/tunnels/item/mtu",
      "nord_mtu_search": "CONFIG_NORD_MTU",
      "nord_mtu_replace": "1500",
      "surf_mtu_search": "CONFIG_SURF_MTU",
      "surf_mtu_replace": "1420",
      "mull_mtu_search": "CONFIG_MULL_MTU",
      "mull_mtu_replace": "1500"
    },
    "wireguard_peer_public_key": {
      "tag": null,
      "plugins": [
        null
        ],
      "xml_string": "./installedpackages/wireguard/peers/item/publickey",
      "nord_public_key_search": "CONFIG_NORD_PUBLIC_KEY",
      "nord_public_key_replace": "NORD_PUBLIC_KEY",
      "surf_public_key_search": "CONFIG_SURF_PUBLIC_KEY",
      "surf_public_key_replace": "SURF_PUBLIC_KEY",
      "mull_public_key_search": "CONFIG_MULL_PUBLIC_KEY",
      "mull_public_key_replace": "MULL_PUBLIC_KEY"
    },
    "wireguard_endpointS": {
      "tag": null,
      "plugins": [
        null
      ],
      "xml_string": "./installedpackages/wireguard/peers/item/endpoint",
      "nordvpn_endpoint_search": "CONFIG_NORDVPN_ENDPOINT",
      "nordvpn_endpoint_replace": "NORDVPN_ENDPOINT",
      "surfshark_endpoint_search": "CONFIG_SURFSHARK_ENDPOINT",
      "surfshark_endpoint_replace": "SURFSHARK_ENDPOINT",
      "mullvad_endpoint_search": "CONFIG_MULLVAD_ENDPOINT",
      "mullvad_endpoint_replace": "MULLVAD_ENDPOINT"
    },
    "dhcp_options": {
      "tag": null,
      "plugins": [
        "base64"
      ],
      "xml_string": "./dhcp/lan/numberoptions/item/value",
      "nfs_search": "192.168.42.8",
      "nfs_replace": "varHOSTIPADDRESS"
    },
    "dhcp_range_from": {
      "tag": null,
      "plugins": [
        null
      ],
      "xml_string": "./dhcp/lan/range",
      "range_start_search": "CONFIG_RANGE_START",
      "range_start_replace": "varRANGE_START",
      "range_end_search": "CONFIG_RANGE_END",
      "range_end_replace": "varRANGE_END"
    },
    "next_server": {
      "tag": null,
      "plugins": [
        null
      ],
      "xml_string": "./dhcp/lan/nextserver",
      "tftp_server_search": "CONFIG_NEXT_SERVER",
      "tftp_server_replace": "varNEXT_SERVER"
    }
  }

Leave a Reply

Your email address will not be published. Required fields are marked *