diff options
Diffstat (limited to 'src')
-rwxr-xr-x | src/conf_mode/load-balancing_reverse-proxy.py | 32 | ||||
-rwxr-xr-x | src/conf_mode/service_dhcp-server.py | 2 | ||||
-rw-r--r-- | src/etc/dhcp/dhclient-enter-hooks.d/04-vyos-resolvconf | 16 | ||||
-rwxr-xr-x | src/helpers/vyos_config_sync.py | 66 | ||||
-rwxr-xr-x | src/services/vyos-http-api-server | 38 |
5 files changed, 96 insertions, 58 deletions
diff --git a/src/conf_mode/load-balancing_reverse-proxy.py b/src/conf_mode/load-balancing_reverse-proxy.py index 7338fe573..2a0acd84a 100755 --- a/src/conf_mode/load-balancing_reverse-proxy.py +++ b/src/conf_mode/load-balancing_reverse-proxy.py @@ -55,6 +55,29 @@ def get_config(config=None): return lb +def _verify_cert(lb: dict, config: dict) -> None: + if 'ca_certificate' in config['ssl']: + ca_name = config['ssl']['ca_certificate'] + pki_ca = lb['pki'].get('ca') + if pki_ca is None: + raise ConfigError(f'CA certificates does not exist in PKI') + else: + ca = pki_ca.get(ca_name) + if ca is None: + raise ConfigError(f'CA certificate "{ca_name}" does not exist') + + elif 'certificate' in config['ssl']: + cert_names = config['ssl']['certificate'] + pki_certs = lb['pki'].get('certificate') + if pki_certs is None: + raise ConfigError(f'Certificates does not exist in PKI') + + for cert_name in cert_names: + pki_cert = pki_certs.get(cert_name) + if pki_cert is None: + raise ConfigError(f'Certificate "{cert_name}" does not exist') + + def verify(lb): if not lb: return None @@ -83,6 +106,15 @@ def verify(lb): if {'send_proxy', 'send_proxy_v2'} <= set(bk_server_conf): raise ConfigError(f'Cannot use both "send-proxy" and "send-proxy-v2" for server "{bk_server}"') + for front, front_config in lb['service'].items(): + if 'ssl' in front_config: + _verify_cert(lb, front_config) + + for back, back_config in lb['backend'].items(): + if 'ssl' in back_config: + _verify_cert(lb, back_config) + + def generate(lb): if not lb: # Delete /run/haproxy/haproxy.cfg diff --git a/src/conf_mode/service_dhcp-server.py b/src/conf_mode/service_dhcp-server.py index ba3d69b07..bf4454fda 100755 --- a/src/conf_mode/service_dhcp-server.py +++ b/src/conf_mode/service_dhcp-server.py @@ -316,7 +316,7 @@ def verify(dhcp): raise ConfigError(f'Invalid CA certificate specified for DHCP high-availability') for address in (dict_search('listen_address', dhcp) or []): - if is_addr_assigned(address): + if is_addr_assigned(address, include_vrf=True): listen_ok = True # no need to probe further networks, we have one that is valid continue diff --git a/src/etc/dhcp/dhclient-enter-hooks.d/04-vyos-resolvconf b/src/etc/dhcp/dhclient-enter-hooks.d/04-vyos-resolvconf index 518abeaec..9a8a53bfd 100644 --- a/src/etc/dhcp/dhclient-enter-hooks.d/04-vyos-resolvconf +++ b/src/etc/dhcp/dhclient-enter-hooks.d/04-vyos-resolvconf @@ -14,14 +14,6 @@ if /usr/bin/systemctl -q is-active vyos-hostsd; then hostsd_changes=y fi - if [ -n "$new_dhcp6_domain_search" ]; then - logmsg info "Deleting search domains with tag \"dhcpv6-$interface\" via vyos-hostsd-client" - $hostsd_client --delete-search-domains --tag "dhcpv6-$interface" - logmsg info "Adding search domain \"$new_dhcp6_domain_search\" with tag \"dhcpv6-$interface\" via vyos-hostsd-client" - $hostsd_client --add-search-domains "$new_dhcp6_domain_search" --tag "dhcpv6-$interface" - hostsd_changes=y - fi - if [ -n "$new_domain_name_servers" ]; then logmsg info "Deleting nameservers with tag \"dhcp-$interface\" via vyos-hostsd-client" $hostsd_client --delete-name-servers --tag "dhcp-$interface" @@ -30,14 +22,6 @@ if /usr/bin/systemctl -q is-active vyos-hostsd; then hostsd_changes=y fi - if [ -n "$new_dhcp6_name_servers" ]; then - logmsg info "Deleting nameservers with tag \"dhcpv6-$interface\" via vyos-hostsd-client" - $hostsd_client --delete-name-servers --tag "dhcpv6-$interface" - logmsg info "Adding nameservers \"$new_dhcp6_name_servers\" with tag \"dhcpv6-$interface\" via vyos-hostsd-client" - $hostsd_client --add-name-servers $new_dhcp6_name_servers --tag "dhcpv6-$interface" - hostsd_changes=y - fi - if [ $hostsd_changes ]; then logmsg info "Applying changes via vyos-hostsd-client" $hostsd_client --apply diff --git a/src/helpers/vyos_config_sync.py b/src/helpers/vyos_config_sync.py index 77f7cd810..0604b2837 100755 --- a/src/helpers/vyos_config_sync.py +++ b/src/helpers/vyos_config_sync.py @@ -21,9 +21,11 @@ import json import requests import urllib3 import logging -from typing import Optional, List, Union, Dict, Any +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 @@ -61,39 +63,45 @@ def post_request(url: str, -def retrieve_config(section: Optional[List[str]] = None) -> Optional[Dict[str, Any]]: +def retrieve_config(sections: List[list[str]]) -> Tuple[Dict[str, Any], Dict[str, Any]]: """Retrieves the configuration from the local server. Args: - section: List[str]: The section of the configuration to retrieve. - Default is None. + sections: List[list[str]]: The list of sections of the configuration + to retrieve, given as list of paths. Returns: - Optional[Dict[str, Any]]: The retrieved configuration as a - dictionary, or None if an error occurred. + 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. """ - if section is None: - section = [] - conf = Config() - config = conf.get_config_dict(section, get_first_key=True) - if config: - return config - return None + 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, - commands: List[Dict[str, Any]]) -> Optional[Dict[str, Any]]: + op: str, + mask: Dict[str, Any], + config: Dict[str, Any]) -> 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. - commands (list): List of set/load commands for request, given as: - [{'op': str, 'path': list[str], 'section': dict}, - ...] + 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. Returns: Optional[Dict[str, Any]]: The response from the remote host as a @@ -107,7 +115,9 @@ def set_remote_config( url = f'https://{address}/configure-section' data = json.dumps({ - 'commands': commands, + 'op': op, + 'mask': mask, + 'config': config, 'key': key }) @@ -140,23 +150,15 @@ def config_sync(secondary_address: str, ) # Sync sections ("nat", "firewall", etc) - commands = [] - 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}") - - d = {'op': mode, 'path': section, 'section': config_json} - commands.append(d) + 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, - commands=commands) + op=mode, + mask=mask_dict, + config=config_dict) logger.debug(f"Set config for sections '{sections}': {set_config}") diff --git a/src/services/vyos-http-api-server b/src/services/vyos-http-api-server index 77870a84c..ecbf6fcf9 100755 --- a/src/services/vyos-http-api-server +++ b/src/services/vyos-http-api-server @@ -140,6 +140,14 @@ class ConfigSectionModel(ApiModel, BaseConfigSectionModel): class ConfigSectionListModel(ApiModel): commands: List[BaseConfigSectionModel] +class BaseConfigSectionTreeModel(BaseModel): + op: StrictStr + mask: Dict + config: Dict + +class ConfigSectionTreeModel(ApiModel, BaseConfigSectionTreeModel): + pass + class RetrieveModel(ApiModel): op: StrictStr path: List[StrictStr] @@ -374,7 +382,7 @@ class MultipartRequest(Request): self.form_err = (400, f"Malformed command '{c}': missing 'op' field") if endpoint not in ('/config-file', '/container-image', - '/image'): + '/image', '/configure-section'): if 'path' not in c: self.form_err = (400, f"Malformed command '{c}': missing 'path' field") @@ -392,12 +400,9 @@ class MultipartRequest(Request): self.form_err = (400, f"Malformed command '{c}': 'value' field must be a string") if endpoint in ('/configure-section'): - if 'section' not in c: - self.form_err = (400, - f"Malformed command '{c}': missing 'section' field") - elif not isinstance(c['section'], dict): + if 'section' not in c and 'config' not in c: self.form_err = (400, - f"Malformed command '{c}': 'section' field must be JSON of dict") + f"Malformed command '{c}': missing 'section' or 'config' field") if 'key' not in forms and 'key' not in merge: self.form_err = (401, "Valid API key is required") @@ -455,7 +460,8 @@ def call_commit(s: ConfigSession): logger.warning(f"ConfigSessionError: {e}") def _configure_op(data: Union[ConfigureModel, ConfigureListModel, - ConfigSectionModel, ConfigSectionListModel], + ConfigSectionModel, ConfigSectionListModel, + ConfigSectionTreeModel], request: Request, background_tasks: BackgroundTasks): session = app.state.vyos_session env = session.get_session_env() @@ -481,7 +487,8 @@ def _configure_op(data: Union[ConfigureModel, ConfigureListModel, try: for c in data: op = c.op - path = c.path + if not isinstance(c, BaseConfigSectionTreeModel): + path = c.path if isinstance(c, BaseConfigureModel): if c.value: @@ -495,6 +502,10 @@ def _configure_op(data: Union[ConfigureModel, ConfigureListModel, elif isinstance(c, BaseConfigSectionModel): section = c.section + elif isinstance(c, BaseConfigSectionTreeModel): + mask = c.mask + config = c.config + if isinstance(c, BaseConfigureModel): if op == 'set': session.set(path, value=value) @@ -514,6 +525,14 @@ def _configure_op(data: Union[ConfigureModel, ConfigureListModel, session.load_section(path, section) else: raise ConfigSessionError(f"'{op}' is not a valid operation") + + elif isinstance(c, BaseConfigSectionTreeModel): + if op == 'set': + session.set_section_tree(config) + elif op == 'load': + session.load_section_tree(mask, config) + else: + raise ConfigSessionError(f"'{op}' is not a valid operation") # end for config = Config(session_env=env) d = get_config_diff(config) @@ -554,7 +573,8 @@ def configure_op(data: Union[ConfigureModel, @app.post('/configure-section') def configure_section_op(data: Union[ConfigSectionModel, - ConfigSectionListModel], + ConfigSectionListModel, + ConfigSectionTreeModel], request: Request, background_tasks: BackgroundTasks): return _configure_op(data, request, background_tasks) |