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
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"
}
}