diff --git a/interface-definitions/service_config-sync.xml.in b/interface-definitions/service_config-sync.xml.in index 9955acfee..9e9dcdb69 100644 --- a/interface-definitions/service_config-sync.xml.in +++ b/interface-definitions/service_config-sync.xml.in @@ -1,104 +1,456 @@ <?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> <leafNode name="timeout"> <properties> <help>Connection API timeout</help> <valueHelp> <format>u32:1-300</format> <description>Connection API timeout</description> </valueHelp> <constraint> <validator name="numeric" argument="--range 1-300"/> </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> - <leafNode name="section"> + <node name="section"> <properties> <help>Section for synchronization</help> - <completionHelp> - <list>nat nat66 firewall</list> - </completionHelp> - <valueHelp> - <format>nat</format> - <description>NAT</description> - </valueHelp> - <valueHelp> - <format>nat66</format> - <description>NAT66</description> - </valueHelp> - <valueHelp> - <format>firewall</format> - <description>firewall</description> - </valueHelp> - <constraint> - <regex>(nat|nat66|firewall)</regex> - </constraint> - <multi/> </properties> - </leafNode> + <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="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> + <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 7cfa8fe88..572fea61f 100755 --- a/src/helpers/vyos_config_sync.py +++ b/src/helpers/vyos_config_sync.py @@ -1,192 +1,200 @@ #!/usr/bin/env python3 # -# Copyright (C) 2023 VyOS maintainers and contributors +# 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, Union, Dict, Any from vyos.config import Config 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(section: str = None) -> Optional[Dict[str, Any]]: """Retrieves the configuration from the local server. Args: section: str: The section of the configuration to retrieve. Default is None. Returns: Optional[Dict[str, Any]]: The retrieved configuration as a dictionary, or None if an error occurred. """ if section is None: section = [] - else: - section = section.split() conf = Config() config = conf.get_config_dict(section, get_first_key=True) if config: return config return None def set_remote_config( address: str, key: str, op: str, path: str = None, section: Optional[str] = None) -> 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. path (Optional[str]): The path of the configuration. Default is None. section (Optional[str]): The section of the configuration to load. Default is None. Returns: Optional[Dict[str, Any]]: The response from the remote host as a dictionary, or None if an error occurred. """ if path is None: path = [] - else: - path = path.split() headers = {'Content-Type': 'application/json'} # Disable the InsecureRequestWarning urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) url = f'https://{address}/configure-section' data = json.dumps({ 'op': mode, 'path': path, 'section': section, '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: str) -> bool: from vyos.config_mgmt import is_node_revised - return is_node_revised([section]) + return is_node_revised(section) def config_sync(secondary_address: str, secondary_key: str, - sections: List[str], + sections: List[list], mode: str): """Retrieve a config section from primary router in JSON format and send it to secondary router """ - # Config sync only if sections changed if not any(map(is_section_revised, sections)): return logger.info( f"Config synchronization: Mode={mode}, Secondary={secondary_address}" ) # Sync sections ("nat", "firewall", etc) for section in sections: config_json = retrieve_config(section=section) # Check if config path deesn't exist, for example "set nat" # we set empty value for config_json data # As we cannot send to the remote host section "nat None" config if not config_json: config_json = "" logger.debug( f"Retrieved config for section '{section}': {config_json}") set_config = set_remote_config(address=secondary_address, key=secondary_key, op=mode, path=section, section=config_json) logger.debug(f"Set config for section '{section}': {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') 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.") 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, - sections, mode) + list_sections, mode)