diff --git a/interface-definitions/service_config-sync.xml.in b/interface-definitions/service_config-sync.xml.in index e9ea9aa4b..648c14aee 100644 --- a/interface-definitions/service_config-sync.xml.in +++ b/interface-definitions/service_config-sync.xml.in @@ -1,524 +1,528 @@ <?xml version="1.0" encoding="UTF-8"?> <interfaceDefinition> <node name="service"> <children> <node name="config-sync" owner="${vyos_conf_scripts_dir}/service_config-sync.py"> <properties> <help>Configuration synchronization</help> </properties> <children> <node name="secondary"> <properties> <help>Secondary server parameters</help> </properties> <children> <leafNode name="address"> <properties> <help>IP address</help> <valueHelp> <format>ipv4</format> <description>IPv4 address to match</description> </valueHelp> <valueHelp> <format>ipv6</format> <description>IPv6 address to match</description> </valueHelp> <valueHelp> <format>hostname</format> <description>FQDN address to match</description> </valueHelp> <constraint> <validator name="ipv4-address"/> <validator name="ipv6-address"/> <validator name="fqdn"/> </constraint> </properties> </leafNode> + #include <include/port-number.xml.i> + <leafNode name="port"> + <defaultValue>443</defaultValue> + </leafNode> <leafNode name="timeout"> <properties> <help>Connection API timeout</help> <valueHelp> <format>u32:1-3600</format> <description>Connection API timeout</description> </valueHelp> <constraint> <validator name="numeric" argument="--range 1-3600"/> </constraint> </properties> <defaultValue>60</defaultValue> </leafNode> <leafNode name="key"> <properties> <help>HTTP API key</help> </properties> </leafNode> </children> </node> <leafNode name="mode"> <properties> <help>Synchronization mode</help> <completionHelp> <list>load set</list> </completionHelp> <valueHelp> <format>load</format> <description>Load and replace configuration section</description> </valueHelp> <valueHelp> <format>set</format> <description>Set configuration section</description> </valueHelp> <constraint> <regex>(load|set)</regex> </constraint> </properties> </leafNode> <node name="section"> <properties> <help>Section for synchronization</help> </properties> <children> <leafNode name="firewall"> <properties> <help>Firewall</help> <valueless/> </properties> </leafNode> <node name="interfaces"> <properties> <help>Interfaces</help> </properties> <children> <leafNode name="bonding"> <properties> <help>Bonding interface</help> <valueless/> </properties> </leafNode> <leafNode name="bridge"> <properties> <help>Bridge interface</help> <valueless/> </properties> </leafNode> <leafNode name="dummy"> <properties> <help>Dummy interface</help> <valueless/> </properties> </leafNode> <leafNode name="ethernet"> <properties> <help>Ethernet interface</help> <valueless/> </properties> </leafNode> <leafNode name="geneve"> <properties> <help>GENEVE interface</help> <valueless/> </properties> </leafNode> <leafNode name="input"> <properties> <help>Input interface</help> <valueless/> </properties> </leafNode> <leafNode name="l2tpv3"> <properties> <help>L2TPv3 interface</help> <valueless/> </properties> </leafNode> <leafNode name="loopback"> <properties> <help>Loopback interface</help> <valueless/> </properties> </leafNode> <leafNode name="macsec"> <properties> <help>MACsec interface</help> <valueless/> </properties> </leafNode> <leafNode name="openvpn"> <properties> <help>OpenVPN interface</help> <valueless/> </properties> </leafNode> <leafNode name="pppoe"> <properties> <help>PPPoE interface</help> <valueless/> </properties> </leafNode> <leafNode name="pseudo-ethernet"> <properties> <help>Pseudo-Ethernet interface</help> <valueless/> </properties> </leafNode> <leafNode name="sstpc"> <properties> <help>SSTP client interface</help> <valueless/> </properties> </leafNode> <leafNode name="tunnel"> <properties> <help>Tunnel interface</help> <valueless/> </properties> </leafNode> <leafNode name="virtual-ethernet"> <properties> <help>Virtual Ethernet interface</help> <valueless/> </properties> </leafNode> <leafNode name="vti"> <properties> <help>Virtual tunnel interface</help> <valueless/> </properties> </leafNode> <leafNode name="vxlan"> <properties> <help>VXLAN interface</help> <valueless/> </properties> </leafNode> <leafNode name="wireguard"> <properties> <help>Wireguard interface</help> <valueless/> </properties> </leafNode> <leafNode name="wireless"> <properties> <help>Wireless interface</help> <valueless/> </properties> </leafNode> <leafNode name="wwan"> <properties> <help>WWAN interface</help> <valueless/> </properties> </leafNode> </children> </node> <leafNode name="nat"> <properties> <help>NAT</help> <valueless/> </properties> </leafNode> <leafNode name="nat66"> <properties> <help>NAT66</help> <valueless/> </properties> </leafNode> <leafNode name="pki"> <properties> <help>Public key infrastructure (PKI)</help> <valueless/> </properties> </leafNode> <leafNode name="policy"> <properties> <help>Routing policy</help> <valueless/> </properties> </leafNode> <node name="protocols"> <properties> <help>Routing protocols</help> </properties> <children> <leafNode name="babel"> <properties> <help>Babel Routing Protocol</help> <valueless/> </properties> </leafNode> <leafNode name="bfd"> <properties> <help>Bidirectional Forwarding Detection (BFD)</help> <valueless/> </properties> </leafNode> <leafNode name="bgp"> <properties> <help>Border Gateway Protocol (BGP)</help> <valueless/> </properties> </leafNode> <leafNode name="failover"> <properties> <help>Failover route</help> <valueless/> </properties> </leafNode> <leafNode name="igmp-proxy"> <properties> <help>Internet Group Management Protocol (IGMP) proxy</help> <valueless/> </properties> </leafNode> <leafNode name="isis"> <properties> <help>Intermediate System to Intermediate System (IS-IS)</help> <valueless/> </properties> </leafNode> <leafNode name="mpls"> <properties> <help>Multiprotocol Label Switching (MPLS)</help> <valueless/> </properties> </leafNode> <leafNode name="nhrp"> <properties> <help>Next Hop Resolution Protocol (NHRP) parameters</help> <valueless/> </properties> </leafNode> <leafNode name="ospf"> <properties> <help>Open Shortest Path First (OSPF)</help> <valueless/> </properties> </leafNode> <leafNode name="ospfv3"> <properties> <help>Open Shortest Path First (OSPF) for IPv6</help> <valueless/> </properties> </leafNode> <leafNode name="pim"> <properties> <help>Protocol Independent Multicast (PIM) and IGMP</help> <valueless/> </properties> </leafNode> <leafNode name="pim6"> <properties> <help>Protocol Independent Multicast for IPv6 (PIMv6) and MLD</help> <valueless/> </properties> </leafNode> <leafNode name="rip"> <properties> <help>Routing Information Protocol (RIP) parameters</help> <valueless/> </properties> </leafNode> <leafNode name="ripng"> <properties> <help>Routing Information Protocol (RIPng) parameters</help> <valueless/> </properties> </leafNode> <leafNode name="rpki"> <properties> <help>Resource Public Key Infrastructure (RPKI)</help> <valueless/> </properties> </leafNode> <leafNode name="segment-routing"> <properties> <help>Segment Routing</help> <valueless/> </properties> </leafNode> <leafNode name="static"> <properties> <help>Static Routing</help> <valueless/> </properties> </leafNode> </children> </node> <node name="qos"> <properties> <help>Quality of Service (QoS)</help> </properties> <children> <leafNode name="interface"> <properties> <help>Interface to apply QoS policy</help> <valueless/> </properties> </leafNode> <leafNode name="policy"> <properties> <help>Service Policy definitions</help> <valueless/> </properties> </leafNode> </children> </node> <node name="service"> <properties> <help>System services</help> </properties> <children> <leafNode name="console-server"> <properties> <help>Serial Console Server</help> <valueless/> </properties> </leafNode> <leafNode name="dhcp-relay"> <properties> <help>Host Configuration Protocol (DHCP) relay agent</help> <valueless/> </properties> </leafNode> <leafNode name="dhcp-server"> <properties> <help>Dynamic Host Configuration Protocol (DHCP) for DHCP server</help> <valueless/> </properties> </leafNode> <leafNode name="dhcpv6-relay"> <properties> <help>DHCPv6 Relay Agent parameters</help> <valueless/> </properties> </leafNode> <leafNode name="dhcpv6-server"> <properties> <help>DHCP for IPv6 (DHCPv6) server</help> <valueless/> </properties> </leafNode> <leafNode name="dns"> <properties> <help>Domain Name System (DNS) related services</help> <valueless/> </properties> </leafNode> <leafNode name="lldp"> <properties> <help>LLDP settings</help> <valueless/> </properties> </leafNode> <leafNode name="mdns"> <properties> <help>Multicast DNS (mDNS) parameters</help> <valueless/> </properties> </leafNode> <leafNode name="monitoring"> <properties> <help>Monitoring services</help> <valueless/> </properties> </leafNode> <leafNode name="ndp-proxy"> <properties> <help>Neighbor Discovery Protocol (NDP) Proxy</help> <valueless/> </properties> </leafNode> <leafNode name="ntp"> <properties> <help>Network Time Protocol (NTP) configuration</help> <valueless/> </properties> </leafNode> <leafNode name="snmp"> <properties> <help>Simple Network Management Protocol (SNMP)</help> <valueless/> </properties> </leafNode> <leafNode name="tftp-server"> <properties> <help>Trivial File Transfer Protocol (TFTP) server</help> <valueless/> </properties> </leafNode> <leafNode name="webproxy"> <properties> <help>Webproxy service settings</help> <valueless/> </properties> </leafNode> </children> </node> <node name="system"> <properties> <help>System parameters</help> </properties> <children> <leafNode name="conntrack"> <properties> <help>Connection Tracking</help> <valueless/> </properties> </leafNode> <leafNode name="flow-accounting"> <properties> <help>Flow accounting</help> <valueless/> </properties> </leafNode> <leafNode name="option"> <properties> <help>System Options</help> <valueless/> </properties> </leafNode> <leafNode name="sflow"> <properties> <help>sFlow</help> <valueless/> </properties> </leafNode> <leafNode name="static-host-mapping"> <properties> <help>Map host names to addresses</help> <valueless/> </properties> </leafNode> <leafNode name="sysctl"> <properties> <help>Configure kernel parameters at runtime</help> <valueless/> </properties> </leafNode> <leafNode name="time-zone"> <properties> <help>Local time zone</help> <valueless/> </properties> </leafNode> </children> </node> <leafNode name="vpn"> <properties> <help>Virtual Private Network (VPN)</help> <valueless/> </properties> </leafNode> <leafNode name="vrf"> <properties> <help>Virtual Routing and Forwarding</help> <valueless/> </properties> </leafNode> </children> </node> </children> </node> </children> </node> </interfaceDefinition> diff --git a/src/helpers/vyos_config_sync.py b/src/helpers/vyos_config_sync.py index 0604b2837..9d9aec376 100755 --- a/src/helpers/vyos_config_sync.py +++ b/src/helpers/vyos_config_sync.py @@ -1,204 +1,205 @@ #!/usr/bin/env python3 # # Copyright (C) 2023-2024 VyOS maintainers and contributors # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License version 2 or later as # published by the Free Software Foundation. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see <http://www.gnu.org/licenses/>. # # import os import json import requests import urllib3 import logging from typing import Optional, List, Tuple, Dict, Any from vyos.config import Config from vyos.configtree import ConfigTree from vyos.configtree import mask_inclusive from vyos.template import bracketize_ipv6 CONFIG_FILE = '/run/config_sync_conf.conf' # Logging logging.basicConfig(level=logging.INFO) logger = logging.getLogger(__name__) logger.name = os.path.basename(__file__) # API API_HEADERS = {'Content-Type': 'application/json'} def post_request(url: str, data: str, headers: Dict[str, str]) -> requests.Response: """Sends a POST request to the specified URL Args: url (str): The URL to send the POST request to. data (Dict[str, Any]): The data to send with the POST request. headers (Dict[str, str]): The headers to include with the POST request. Returns: requests.Response: The response object representing the server's response to the request """ response = requests.post(url, data=data, headers=headers, verify=False, timeout=timeout) return response def retrieve_config(sections: List[list[str]]) -> Tuple[Dict[str, Any], Dict[str, Any]]: """Retrieves the configuration from the local server. Args: sections: List[list[str]]: The list of sections of the configuration to retrieve, given as list of paths. Returns: Tuple[Dict[str, Any],Dict[str,Any]]: The tuple (mask, config) where: - mask: The tree of paths of sections, as a dictionary. - config: The subtree of masked config data, as a dictionary. """ mask = ConfigTree('') for section in sections: mask.set(section) mask_dict = json.loads(mask.to_json()) config = Config() config_tree = config.get_config_tree() masked = mask_inclusive(config_tree, mask) config_dict = json.loads(masked.to_json()) return mask_dict, config_dict def set_remote_config( address: str, key: str, op: str, mask: Dict[str, Any], - config: Dict[str, Any]) -> Optional[Dict[str, Any]]: + config: Dict[str, Any], + port: int) -> Optional[Dict[str, Any]]: """Loads the VyOS configuration in JSON format to a remote host. Args: address (str): The address of the remote host. key (str): The key to use for loading the configuration. op (str): The operation to perform (set or load). mask (dict): The dict of paths in sections. config (dict): The dict of masked config data. + port (int): The remote API port Returns: Optional[Dict[str, Any]]: The response from the remote host as a dictionary, or None if a RequestException occurred. """ headers = {'Content-Type': 'application/json'} # Disable the InsecureRequestWarning urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) - url = f'https://{address}/configure-section' + url = f'https://{address}:{port}/configure-section' data = json.dumps({ 'op': op, 'mask': mask, 'config': config, 'key': key }) try: config = post_request(url, data, headers) return config.json() except requests.exceptions.RequestException as e: print(f"An error occurred: {e}") logger.error(f"An error occurred: {e}") return None def is_section_revised(section: List[str]) -> bool: from vyos.config_mgmt import is_node_revised return is_node_revised(section) def config_sync(secondary_address: str, secondary_key: str, sections: List[list[str]], - mode: str): + mode: str, + secondary_port: int): """Retrieve a config section from primary router in JSON format and send it to secondary router """ if not any(map(is_section_revised, sections)): return logger.info( f"Config synchronization: Mode={mode}, Secondary={secondary_address}" ) # Sync sections ("nat", "firewall", etc) mask_dict, config_dict = retrieve_config(sections) logger.debug( f"Retrieved config for sections '{sections}': {config_dict}") set_config = set_remote_config(address=secondary_address, key=secondary_key, op=mode, mask=mask_dict, - config=config_dict) + config=config_dict, + port=secondary_port) logger.debug(f"Set config for sections '{sections}': {set_config}") if __name__ == '__main__': # Read configuration from file if not os.path.exists(CONFIG_FILE): logger.error(f"Post-commit: No config file '{CONFIG_FILE}' exists") exit(0) with open(CONFIG_FILE, 'r') as f: config_data = f.read() config = json.loads(config_data) mode = config.get('mode') secondary_address = config.get('secondary', {}).get('address') secondary_address = bracketize_ipv6(secondary_address) secondary_key = config.get('secondary', {}).get('key') + secondary_port = int(config.get('secondary', {}).get('port', 443)) sections = config.get('section') timeout = int(config.get('secondary', {}).get('timeout')) - if not all([ - mode, secondary_address, secondary_key, sections - ]): - logger.error( - "Missing required configuration data for config synchronization.") + if not all([mode, secondary_address, secondary_key, sections]): + logger.error("Missing required configuration data for config synchronization.") exit(0) # Generate list_sections of sections/subsections # [ # ['interfaces', 'pseudo-ethernet'], ['interfaces', 'virtual-ethernet'], ['nat'], ['nat66'] # ] list_sections = [] for section, subsections in sections.items(): if subsections: for subsection in subsections: list_sections.append([section, subsection]) else: list_sections.append([section]) - config_sync(secondary_address, secondary_key, - list_sections, mode) + config_sync(secondary_address, secondary_key, list_sections, mode, secondary_port)