diff options
-rw-r--r-- | data/templates/openvpn/server.conf.j2 | 2 | ||||
-rw-r--r-- | interface-definitions/dns-forwarding.xml.in | 19 | ||||
-rw-r--r-- | interface-definitions/include/radius-server-ipv4-ipv6.xml.i | 22 | ||||
-rw-r--r-- | interface-definitions/include/source-address-ipv4-ipv6-multi.xml.i | 22 | ||||
-rw-r--r-- | interface-definitions/system-login.xml.in | 16 | ||||
-rw-r--r-- | python/vyos/progressbar.py | 33 | ||||
-rw-r--r-- | python/vyos/remote.py | 43 | ||||
-rw-r--r-- | python/vyos/utils/io.py | 10 | ||||
-rwxr-xr-x | src/migration-scripts/firewall/12-to-13 | 2 | ||||
-rwxr-xr-x | src/migration-scripts/policy/6-to-7 | 2 | ||||
-rwxr-xr-x | src/op_mode/generate_firewall_rule-resequence.py | 4 |
11 files changed, 77 insertions, 98 deletions
diff --git a/data/templates/openvpn/server.conf.j2 b/data/templates/openvpn/server.conf.j2 index 746155c37..c02411904 100644 --- a/data/templates/openvpn/server.conf.j2 +++ b/data/templates/openvpn/server.conf.j2 @@ -79,7 +79,7 @@ server {{ subnet | address_from_cidr }} {{ subnet | netmask_from_cidr }} {{ 'nop {% if server.push_route is vyos_defined %} {% for route, route_config in server.push_route.items() %} {% if route | is_ipv4 %} -push "route {{ route | address_from_cidr }} {{ route | netmask_from_cidr }} {{ subnet | first_host_address ~ ' ' ~ route_config.metric if route_config.metric is vyos_defined }}" +push "route {{ route | address_from_cidr }} {{ route | netmask_from_cidr }} {{ 'vpn_gateway' ~ ' ' ~ route_config.metric if route_config.metric is vyos_defined }}" {% elif route | is_ipv6 %} push "route-ipv6 {{ route }}" {% endif %} diff --git a/interface-definitions/dns-forwarding.xml.in b/interface-definitions/dns-forwarding.xml.in index c4295317a..5ca02acef 100644 --- a/interface-definitions/dns-forwarding.xml.in +++ b/interface-definitions/dns-forwarding.xml.in @@ -684,25 +684,8 @@ <defaultValue>1500</defaultValue> </leafNode> #include <include/name-server-ipv4-ipv6-port.xml.i> + #include <include/source-address-ipv4-ipv6-multi.xml.i> <leafNode name="source-address"> - <properties> - <help>Local addresses from which to send DNS queries</help> - <completionHelp> - <script>${vyos_completion_dir}/list_local_ips.sh --both</script> - </completionHelp> - <valueHelp> - <format>ipv4</format> - <description>IPv4 address from which to send traffic</description> - </valueHelp> - <valueHelp> - <format>ipv6</format> - <description>IPv6 address from which to send traffic</description> - </valueHelp> - <multi/> - <constraint> - <validator name="ip-address"/> - </constraint> - </properties> <defaultValue>0.0.0.0 ::</defaultValue> </leafNode> <leafNode name="system"> diff --git a/interface-definitions/include/radius-server-ipv4-ipv6.xml.i b/interface-definitions/include/radius-server-ipv4-ipv6.xml.i index a0cdcd7c3..e454b9025 100644 --- a/interface-definitions/include/radius-server-ipv4-ipv6.xml.i +++ b/interface-definitions/include/radius-server-ipv4-ipv6.xml.i @@ -25,27 +25,7 @@ #include <include/radius-server-auth-port.xml.i> </children> </tagNode> - <leafNode name="source-address"> - <properties> - <help>Source IP address used to initiate connection</help> - <completionHelp> - <script>${vyos_completion_dir}/list_local_ips.sh --both</script> - </completionHelp> - <valueHelp> - <format>ipv4</format> - <description>IPv4 source address</description> - </valueHelp> - <valueHelp> - <format>ipv6</format> - <description>IPv6 source address</description> - </valueHelp> - <constraint> - <validator name="ipv4-address"/> - <validator name="ipv6-address"/> - </constraint> - <multi/> - </properties> - </leafNode> + #include <include/source-address-ipv4-ipv6-multi.xml.i> <leafNode name="security-mode"> <properties> <help>Security mode for RADIUS authentication</help> diff --git a/interface-definitions/include/source-address-ipv4-ipv6-multi.xml.i b/interface-definitions/include/source-address-ipv4-ipv6-multi.xml.i new file mode 100644 index 000000000..d56ca5be6 --- /dev/null +++ b/interface-definitions/include/source-address-ipv4-ipv6-multi.xml.i @@ -0,0 +1,22 @@ +<!-- include start from source-address-ipv4-ipv6-multi.xml.i --> +<leafNode name="source-address"> + <properties> + <help>Source IP address used to initiate connection</help> + <completionHelp> + <script>${vyos_completion_dir}/list_local_ips.sh --both</script> + </completionHelp> + <valueHelp> + <format>ipv4</format> + <description>IPv4 source address</description> + </valueHelp> + <valueHelp> + <format>ipv6</format> + <description>IPv6 source address</description> + </valueHelp> + <constraint> + <validator name="ip-address"/> + </constraint> + <multi/> + </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/system-login.xml.in b/interface-definitions/system-login.xml.in index 30fea91b0..a2f8beead 100644 --- a/interface-definitions/system-login.xml.in +++ b/interface-definitions/system-login.xml.in @@ -244,21 +244,7 @@ </leafNode> </children> </tagNode> - <leafNode name="source-address"> - <properties> - <help>Source IP used to communicate with TACACS+ server</help> - <completionHelp> - <script>${vyos_completion_dir}/list_local_ips.sh --ipv4</script> - </completionHelp> - <valueHelp> - <format>ipv4</format> - <description>IPv4 source address</description> - </valueHelp> - <constraint> - <validator name="ipv4-address"/> - </constraint> - </properties> - </leafNode> + #include <include/source-address-ipv4.xml.i> <leafNode name="security-mode"> <properties> <help>Security mode for TACACS+ authentication</help> diff --git a/python/vyos/progressbar.py b/python/vyos/progressbar.py index 1793c445b..7bc9d9856 100644 --- a/python/vyos/progressbar.py +++ b/python/vyos/progressbar.py @@ -19,26 +19,35 @@ import signal import subprocess import sys +from vyos.utils.io import is_dumb_terminal from vyos.utils.io import print_error + class Progressbar: def __init__(self, step=None): self.total = 0.0 self.step = step + # Silently ignore all calls if terminal capabilities are lacking. + # This will also prevent the output from littering Ansible logs, + # as `ansible.netcommon.network_cli' coaxes the terminal into believing + # it is interactive. + self._dumb = is_dumb_terminal() def __enter__(self): - # Recalculate terminal width with every window resize. - signal.signal(signal.SIGWINCH, lambda signum, frame: self._update_cols()) - # Disable line wrapping to prevent the staircase effect. - subprocess.run(['tput', 'rmam'], check=False) - self._update_cols() - # Print an empty progressbar with entry. - self.progress(0, 1) + if not self._dumb: + # Recalculate terminal width with every window resize. + signal.signal(signal.SIGWINCH, lambda signum, frame: self._update_cols()) + # Disable line wrapping to prevent the staircase effect. + subprocess.run(['tput', 'rmam'], check=False) + self._update_cols() + # Print an empty progressbar with entry. + self.progress(0, 1) return self def __exit__(self, exc_type, kexc_val, exc_tb): - # Revert to the default SIGWINCH handler (ie nothing). - signal.signal(signal.SIGWINCH, signal.SIG_DFL) - # Reenable line wrapping. - subprocess.run(['tput', 'smam'], check=False) + if not self._dumb: + # Revert to the default SIGWINCH handler (ie nothing). + signal.signal(signal.SIGWINCH, signal.SIG_DFL) + # Reenable line wrapping. + subprocess.run(['tput', 'smam'], check=False) def _update_cols(self): # `os.get_terminal_size()' is fast enough for our purposes. self.col = max(os.get_terminal_size().columns - 15, 20) @@ -60,7 +69,7 @@ class Progressbar: Stateless progressbar taking no input at init and current progress with final size at callback (for SSH) """ - if done <= total: + if done <= total and not self._dumb: length = math.ceil(self.col * done / total) percentage = str(math.ceil(100 * done / total)).rjust(3) # Carriage return at the end will make sure the line will get overwritten. diff --git a/python/vyos/remote.py b/python/vyos/remote.py index 1ca8a9530..4be477d24 100644 --- a/python/vyos/remote.py +++ b/python/vyos/remote.py @@ -34,6 +34,7 @@ from requests.packages.urllib3 import PoolManager from vyos.progressbar import Progressbar from vyos.utils.io import ask_yes_no +from vyos.utils.io import is_interactive from vyos.utils.io import print_error from vyos.utils.misc import begin from vyos.utils.process import cmd @@ -49,7 +50,7 @@ class InteractivePolicy(MissingHostKeyPolicy): def missing_host_key(self, client, hostname, key): print_error(f"Host '{hostname}' not found in known hosts.") print_error('Fingerprint: ' + key.get_fingerprint().hex()) - if sys.stdout.isatty() and ask_yes_no('Do you wish to continue?'): + if is_interactive() and ask_yes_no('Do you wish to continue?'): if client._host_keys_filename\ and ask_yes_no('Do you wish to permanently add this host/key pair to known hosts?'): client._host_keys.add(hostname, key.get_name(), key) @@ -250,7 +251,6 @@ class HttpC: allow_redirects=True, timeout=self.timeout) as r: # Abort early if the destination is inaccessible. - print('pre-3') r.raise_for_status() # If the request got redirected, keep the last URL we ended up with. final_urlstring = r.url @@ -323,17 +323,25 @@ def urlc(urlstring, *args, **kwargs): except KeyError: raise ValueError(f'Unsupported URL scheme: "{url.scheme}"') -def download(local_path, urlstring, *args, **kwargs): +def download(local_path, urlstring, progressbar=False, check_space=False, + source_host='', source_port=0, timeout=10.0): try: - urlc(urlstring, *args, **kwargs).download(local_path) + progressbar = progressbar and is_interactive() + urlc(urlstring, progressbar, check_space, source_host, source_port, timeout).download(local_path) except Exception as err: print_error(f'Unable to download "{urlstring}": {err}') + except KeyboardInterrupt: + print_error('\nDownload aborted by user.') -def upload(local_path, urlstring, *args, **kwargs): +def upload(local_path, urlstring, progressbar=False, + source_host='', source_port=0, timeout=10.0): try: - urlc(urlstring, *args, **kwargs).upload(local_path) + progressbar = progressbar and is_interactive() + urlc(urlstring, progressbar, source_host, source_port, timeout).upload(local_path) except Exception as err: print_error(f'Unable to upload "{urlstring}": {err}') + except KeyboardInterrupt: + print_error('\nUpload aborted by user.') def get_remote_config(urlstring, source_host='', source_port=0): """ @@ -346,26 +354,3 @@ def get_remote_config(urlstring, source_host='', source_port=0): return f.read() finally: os.remove(temp) - -def friendly_download(local_path, urlstring, source_host='', source_port=0): - """ - Download with a progress bar, reassuring messages and free space checks. - """ - try: - print_error('Downloading...') - download(local_path, urlstring, True, True, source_host, source_port) - except KeyboardInterrupt: - print_error('\nDownload aborted by user.') - sys.exit(1) - except: - import traceback - print_error(f'Failed to download {urlstring}.') - # There are a myriad different reasons a download could fail. - # SSH errors, FTP errors, I/O errors, HTTP errors (403, 404...) - # We omit the scary stack trace but print the error nevertheless. - exc_type, exc_value, exc_traceback = sys.exc_info() - traceback.print_exception(exc_type, exc_value, None, 0, None, False) - sys.exit(1) - else: - print_error('Download complete.') - sys.exit(0) diff --git a/python/vyos/utils/io.py b/python/vyos/utils/io.py index 5fffa62f8..8790cbaac 100644 --- a/python/vyos/utils/io.py +++ b/python/vyos/utils/io.py @@ -62,3 +62,13 @@ def ask_yes_no(question, default=False) -> bool: stdout.write("Please respond with yes/y or no/n\n") except EOFError: stdout.write("\nPlease respond with yes/y or no/n\n") + +def is_interactive(): + """Try to determine if the routine was called from an interactive shell.""" + import os, sys + return os.getenv('TERM', default=False) and sys.stderr.isatty() and sys.stdout.isatty() + +def is_dumb_terminal(): + """Check if the current TTY is dumb, so that we can disable advanced terminal features.""" + import os + return os.getenv('TERM') in ['vt100', 'dumb'] diff --git a/src/migration-scripts/firewall/12-to-13 b/src/migration-scripts/firewall/12-to-13 index c2b34b2d8..4eaae779b 100755 --- a/src/migration-scripts/firewall/12-to-13 +++ b/src/migration-scripts/firewall/12-to-13 @@ -70,7 +70,7 @@ for family in ['ipv4', 'ipv6', 'bridge']: state_value = config.return_value(base + [family, hook, priority, 'rule', rule, 'state', state]) config.delete(base + [family, hook, priority, 'rule', rule, 'state', state]) if state_value == 'enable': - config.set(base + [family, hook, priority, 'rule', rule, 'state', state]) + config.set(base + [family, hook, priority, 'rule', rule, 'state'], value=state, replace=False) flag_enable = 'True' if flag_enable == 'False': config.delete(base + [family, hook, priority, 'rule', rule, 'state']) diff --git a/src/migration-scripts/policy/6-to-7 b/src/migration-scripts/policy/6-to-7 index 1f955aa02..727b8487a 100755 --- a/src/migration-scripts/policy/6-to-7 +++ b/src/migration-scripts/policy/6-to-7 @@ -66,7 +66,7 @@ for family in ['route', 'route6']: state_value = config.return_value(base + [family, policy_name, 'rule', rule, 'state', state]) config.delete(base + [family, policy_name, 'rule', rule, 'state', state]) if state_value == 'enable': - config.set(base + [family, policy_name, 'rule', rule, 'state', state]) + config.set(base + [family, policy_name, 'rule', rule, 'state'], value=state, replace=False) flag_enable = 'True' if flag_enable == 'False': config.delete(base + [family, policy_name, 'rule', rule, 'state']) diff --git a/src/op_mode/generate_firewall_rule-resequence.py b/src/op_mode/generate_firewall_rule-resequence.py index eb82a1a0a..21441f689 100755 --- a/src/op_mode/generate_firewall_rule-resequence.py +++ b/src/op_mode/generate_firewall_rule-resequence.py @@ -41,6 +41,10 @@ def convert_to_set_commands(config_dict, parent_key=''): commands.extend( convert_to_set_commands(value, f"{current_key} ")) + elif isinstance(value, list): + for item in value: + commands.append(f"set {current_key} '{item}'") + elif isinstance(value, str): commands.append(f"set {current_key} '{value}'") |