diff options
-rw-r--r-- | Makefile | 5 | ||||
-rw-r--r-- | debian/control | 4 | ||||
-rw-r--r-- | interface-definitions/beep-on-boot.xml | 21 | ||||
-rw-r--r-- | interface-definitions/snmp.xml | 4 | ||||
-rw-r--r-- | interface-definitions/ssh.xml | 16 | ||||
-rw-r--r-- | python/vyos/authutils.py | 43 | ||||
-rw-r--r-- | python/vyos/initialsetup.py | 72 | ||||
-rw-r--r-- | python/vyos/util.py | 51 | ||||
-rwxr-xr-x | src/conf_mode/beep_if_fully_booted.py | 42 | ||||
-rwxr-xr-x | src/conf_mode/snmp.py | 105 | ||||
-rwxr-xr-x | src/helpers/commands-pipe.py | 3 | ||||
-rwxr-xr-x | src/op_mode/cpu_summary.py | 2 | ||||
-rwxr-xr-x | src/op_mode/dns_forwarding_restart.sh | 2 | ||||
-rw-r--r-- | src/tests/test_initial_setup.py | 103 | ||||
-rw-r--r-- | src/utils/initial-setup | 40 | ||||
-rwxr-xr-x | src/validators/numeric | 8 | ||||
-rw-r--r-- | tests/data/config.boot.default | 40 |
17 files changed, 510 insertions, 51 deletions
@@ -6,10 +6,11 @@ OP_TMPL_DIR := templates-op interface_definitions: mkdir -p $(TMPL_DIR) - find $(CURDIR)/interface-definitions/ -type f | xargs -I {} $(CURDIR)/scripts/build-command-templates {} $(CURDIR)/schema/interface_definition.rng $(TMPL_DIR) || exit 1 + find $(CURDIR)/interface-definitions/ -type f -name "*.xml" | xargs -I {} $(CURDIR)/scripts/build-command-templates {} $(CURDIR)/schema/interface_definition.rng $(TMPL_DIR) || exit 1 # XXX: delete top level node.def's that now live in other packages rm -f $(TMPL_DIR)/system/node.def + rm -f $(TMPL_DIR)/system/options/node.def rm -f $(TMPL_DIR)/service/node.def rm -f $(TMPL_DIR)/service/dns/node.def rm -f $(TMPL_DIR)/protocols/node.def @@ -19,7 +20,7 @@ interface_definitions: op_mode_definitions: mkdir -p $(OP_TMPL_DIR) - find $(CURDIR)/op-mode-definitions/ -type f | xargs -I {} $(CURDIR)/scripts/build-command-op-templates {} $(CURDIR)/schema/op-mode-definition.rng $(OP_TMPL_DIR) || exit 1 + find $(CURDIR)/op-mode-definitions/ -type f -name "*.xml" | xargs -I {} $(CURDIR)/scripts/build-command-op-templates {} $(CURDIR)/schema/op-mode-definition.rng $(OP_TMPL_DIR) || exit 1 # XXX: delete top level op mode node.def's that now live in other packages rm -f $(OP_TMPL_DIR)/show/node.def diff --git a/debian/control b/debian/control index 9b510cc5a..8a6bf3da6 100644 --- a/debian/control +++ b/debian/control @@ -19,6 +19,8 @@ Depends: python3, python3-netifaces, python3-jinja2, python3-pystache, + python3-psutil, + python3-tabulate, ipaddrcheck, tcpdump, bmon, @@ -32,6 +34,8 @@ Depends: python3, ntp, iputils-arping, libvyosconfig0, + beep, + keepalived, ${shlibs:Depends}, ${misc:Depends} Description: VyOS configuration scripts and data diff --git a/interface-definitions/beep-on-boot.xml b/interface-definitions/beep-on-boot.xml new file mode 100644 index 000000000..0da7d0de4 --- /dev/null +++ b/interface-definitions/beep-on-boot.xml @@ -0,0 +1,21 @@ +<?xml version="1.0"?> + +<!-- beep once the login target has been reached --> + +<interfaceDefinition> + <node name="system"> + <children> + <node name="options"> + <children> + <leafNode name="beep-if-fully-booted" owner="${vyos_conf_scripts_dir}/beep_if_fully_booted.py"> + <properties> + <help>plays sound via system speaker when you can login</help> + <valueless/> + <priority>9999</priority> + </properties> + </leafNode> + </children> + </node> + </children> + </node> +</interfaceDefinition> diff --git a/interface-definitions/snmp.xml b/interface-definitions/snmp.xml index 76885a02a..103aa39d7 100644 --- a/interface-definitions/snmp.xml +++ b/interface-definitions/snmp.xml @@ -13,9 +13,9 @@ <properties> <help>Community name [REQUIRED]</help> <constraint> - <regex>^[^%]+$</regex> + <regex>^[a-zA-Z0-9\-_]{1,100}</regex> </constraint> - <constraintErrorMessage>Community string may not contain '%'</constraintErrorMessage> + <constraintErrorMessage>Community string is limited to alphanumerical characters only with a total lenght of 100</constraintErrorMessage> </properties> <children> <leafNode name="authorization"> diff --git a/interface-definitions/ssh.xml b/interface-definitions/ssh.xml index 9b3a2fddc..e8786d202 100644 --- a/interface-definitions/ssh.xml +++ b/interface-definitions/ssh.xml @@ -21,12 +21,20 @@ <leafNode name="group"> <properties> <help>Allow members of a group to login</help> + <constraint> + <regex>^[a-z_][a-z0-9_-]{1,31}[$]?</regex> + </constraint> + <constraintErrorMessage>illegal characters or more than 32 characters</constraintErrorMessage> <multi/> </properties> </leafNode> <leafNode name="user"> <properties> <help>Allow specific users to login</help> + <constraint> + <regex>^[a-z_][a-z0-9_-]{1,31}[$]?</regex> + </constraint> + <constraintErrorMessage>illegal characters or more than 32 characters</constraintErrorMessage> <multi/> </properties> </leafNode> @@ -37,12 +45,20 @@ <leafNode name="group"> <properties> <help>Disallow members of a group to login</help> + <constraint> + <regex>^[a-z_][a-z0-9_-]{1,31}[$]?</regex> + </constraint> + <constraintErrorMessage>illegal characters or more than 32 characters</constraintErrorMessage> <multi/> </properties> </leafNode> <leafNode name="user"> <properties> <help>Disallow specific users to login</help> + <constraint> + <regex>^[a-z_][a-z0-9_-]{1,31}[$]?</regex> + </constraint> + <constraintErrorMessage>illegal characters or more than 32 characters</constraintErrorMessage> <multi/> </properties> </leafNode> diff --git a/python/vyos/authutils.py b/python/vyos/authutils.py new file mode 100644 index 000000000..234294649 --- /dev/null +++ b/python/vyos/authutils.py @@ -0,0 +1,43 @@ +# authutils -- miscelanneous functions for handling passwords and publis keys +# +# Copyright (C) 2018 VyOS maintainers and contributors +# +# This library is free software; you can redistribute it and/or modify it under the terms of +# the GNU Lesser General Public License as published by the Free Software Foundation; +# either version 2.1 of the License, or (at your option) any later version. +# +# This library 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 Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License along with this library; +# if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +import re + +from subprocess import Popen, PIPE, STDOUT + + +def make_password_hash(password): + """ Makes a password hash for /etc/shadow using mkpasswd """ + + mkpasswd = Popen(['mkpasswd', '--method=sha-512', '--stdin'], stdout=PIPE, stdin=PIPE, stderr=PIPE) + hash = mkpasswd.communicate(input=password.encode(), timeout=5)[0].decode().strip() + + return hash + +def split_ssh_public_key(key_string, defaultname=""): + """ Splits an SSH public key into its components """ + + key_string = key_string.strip() + parts = re.split(r'\s+', key_string) + + if len(parts) == 3: + key_type, key_data, key_name = parts[0], parts[1], parts[2] + else: + key_type, key_data, key_name = parts[0], parts[1], defaultname + + if key_type not in ['ssh-rsa', 'ssh-dss', 'ecdsa-sha2-nistp256', 'ecdsa-sha2-nistp384', 'ecdsa-sha2-nistp521', 'ssh-ed25519']: + raise ValueError("Bad key type \'{0}\', must be one of must be one of ssh-rsa, ssh-dss, ecdsa-sha2-nistp<256|384|521> or ssh-ed25519".format(key_type)) + + return({"type": key_type, "data": key_data, "name": key_name}) diff --git a/python/vyos/initialsetup.py b/python/vyos/initialsetup.py new file mode 100644 index 000000000..574e7892d --- /dev/null +++ b/python/vyos/initialsetup.py @@ -0,0 +1,72 @@ +# initialsetup -- functions for setting common values in config file, +# for use in installation and first boot scripts +# +# Copyright (C) 2018 VyOS maintainers and contributors +# +# This library is free software; you can redistribute it and/or modify it under the terms of +# the GNU Lesser General Public License as published by the Free Software Foundation; +# either version 2.1 of the License, or (at your option) any later version. +# +# This library 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 Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License along with this library; +# if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +import vyos.configtree +import vyos.authutils + +def set_interface_address(config, intf, addr, intf_type="ethernet"): + config.set(["interfaces", intf_type, intf, "address"], value=addr) + config.set_tag(["interfaces", intf_type]) + +def set_host_name(config, hostname): + config.set(["system", "host-name"], value=hostname) + +def set_name_servers(config, servers): + for s in servers: + config.set(["system", "name-server"], replace=False, value=s) + +def set_default_gateway(config, gateway): + config.set(["protocols", "static", "route", "0.0.0.0/0", "next-hop", gateway]) + config.set_tag(["protocols", "static", "route"]) + config.set_tag(["protocols", "static", "route", "0.0.0.0/0", "next-hop"]) + +def set_user_password(config, user, password): + # Make a password hash + hash = vyos.authutils.make_password_hash(password) + + config.set(["system", "login", "user", user, "authentication", "encrypted-password"], value=hash) + config.set(["system", "login", "user", user, "authentication", "plaintext-password"], value="") + +def disable_user_password(config, user): + config.set(["system", "login", "user", user, "authentication", "encrypted-password"], value="!") + config.set(["system", "login", "user", user, "authentication", "plaintext-password"], value="") + +def set_user_level(config, user, level): + config.set(["system", "login", "user", user, "level"], value=level) + +def set_user_ssh_key(config, user, key_string): + key = vyos.authutils.split_ssh_public_key(key_string, defaultname=user) + + config.set(["system", "login", "user", user, "authentication", "public-keys", key["name"], "key"], value=key["data"]) + config.set(["system", "login", "user", user, "authentication", "public-keys", key["name"], "type"], value=key["type"]) + config.set_tag(["system", "login", "user", user, "authentication", "public-keys"]) + +def create_user(config, user, password=None, key=None, level="admin"): + config.set(["system", "login", "user", user]) + config.set_tag(["system", "login", "user", user]) + + if not key and not password: + raise ValueError("Must set at least password or SSH public key") + + if password: + set_user_password(config, user, password) + else: + disable_user_password(config, user) + + if key: + set_user_ssh_key(config, user, key) + + set_user_level(config, user, level) diff --git a/python/vyos/util.py b/python/vyos/util.py index 8b3de7999..3c454ed4a 100644 --- a/python/vyos/util.py +++ b/python/vyos/util.py @@ -14,7 +14,13 @@ # License along with this library. If not, see <http://www.gnu.org/licenses/>. import re +import psutil +def read_file(path): + """ Read a file to string """ + with open(path, 'r') as f: + data = f.read().strip() + return data def colon_separated_to_dict(data_string, uniquekeys=False): """ Converts a string containing newline-separated entries @@ -63,3 +69,48 @@ def colon_separated_to_dict(data_string, uniquekeys=False): pass return data + +def process_running(pid_file): + """ Checks if a process with PID in pid_file is running """ + with open(pid_file, 'r') as f: + pid = f.read().strip() + return psutil.pid_exists(int(pid)) + +def seconds_to_human(s, separator=""): + """ Converts number of seconds passed to a human-readable + interval such as 1w4d18h35m59s + """ + s = int(s) + + week = 60 * 60 * 24 * 7 + day = 60 * 60 * 24 + hour = 60 * 60 + + remainder = 0 + result = "" + + weeks = s // week + if weeks > 0: + result = "{0}w".format(weeks) + s = s % week + + days = s // day + if days > 0: + result = "{0}{1}{2}d".format(result, separator, days) + s = s % day + + hours = s // hour + if hours > 0: + result = "{0}{1}{2}h".format(result, separator, hours) + s = s % hour + + minutes = s // 60 + if minutes > 0: + result = "{0}{1}{2}m".format(result, separator, minutes) + s = s % 60 + + seconds = s + if seconds > 0: + result = "{0}{1}{2}s".format(result, separator, seconds) + + return result diff --git a/src/conf_mode/beep_if_fully_booted.py b/src/conf_mode/beep_if_fully_booted.py new file mode 100755 index 000000000..f00fcabd0 --- /dev/null +++ b/src/conf_mode/beep_if_fully_booted.py @@ -0,0 +1,42 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2018 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 sys +import os + +from vyos.config import Config +from vyos import ConfigError + +def get_config(): + conf = Config() + if not conf.exists('system options beep-if-fully-booted'): + return None + + return True + +def apply(status): + if status is not None: + os.system('/usr/bin/beep -f 130 -l 100 -n -f 262 -l 100 -n -f 330 -l 100 -n -f 392 -l 100 -n -f 523 -l 100 -n -f 660 -l 100 -n -f 784 -l 300 -n -f 660 -l 300') + +if __name__ == '__main__': + try: + c = get_config() + apply(c) + except ConfigError as e: + print(e) + sys.exit(1) diff --git a/src/conf_mode/snmp.py b/src/conf_mode/snmp.py index 863f7e2e2..001e6c6b4 100755 --- a/src/conf_mode/snmp.py +++ b/src/conf_mode/snmp.py @@ -74,7 +74,9 @@ user_config_tmpl = """ # user {% if v3_users %} {% for u in v3_users %} -{% if u.authPassword %} +{% if u.authOID == 'none' %} +createUser {{ u.name }} +{% elif u.authPassword %} createUser {{ u.name }} {{ u.authProtocol | upper }} "{{ u.authPassword }}" {{ u.privProtocol | upper }} {{ u.privPassword }} {% else %} usmUser 1 3 {{ u.engineID }} "{{ u.name }}" "{{ u.name }}" NULL {{ u.authOID }} {{ u.authMasterKey }} {{ u.privOID }} {{ u.privMasterKey }} 0x @@ -83,6 +85,7 @@ usmUser 1 3 {{ u.engineID }} "{{ u.name }}" "{{ u.name }}" NULL {{ u.authOID }} {% endif %} createUser {{ vyos_user }} MD5 "{{ vyos_user_pass }}" DES +oldEngineID {{ v3_engineid }} """ # SNMPS template - be careful if you edit the template. @@ -135,13 +138,13 @@ SysDescr {{ description }} # Listen agentaddress unix:/run/snmpd.socket{% if listen_on %}{% for li in listen_on %},{{ li }}{% endfor %}{% else %},udp:161,udp6:161{% endif %}{% if v3_tsm_key %},tlstcp:{{ v3_tsm_port }},dtlsudp::{{ v3_tsm_port }}{% endif %} - # SNMP communities {% if communities -%} {% for c in communities %} {% if c.network -%} {% for network in c.network %} {{ c.authorization }}community {{ c.name }} {{ network }} +{{ c.authorization }}community6 {{ c.name }} {{ network }} {% endfor %} {% else %} {{ c.authorization }}community {{ c.name }} @@ -468,8 +471,8 @@ def get_config(): 'name': user, 'authMasterKey': '', 'authPassword': '', - 'authProtocol': '', - 'authOID': '', + 'authProtocol': 'md5', + 'authOID': 'none', 'engineID': '', 'group': '', 'mode': 'ro', @@ -477,7 +480,7 @@ def get_config(): 'privPassword': '', 'privOID': '', 'privTsmKey': '', - 'privProtocol': '' + 'privProtocol': 'des' } # @@ -489,10 +492,14 @@ def get_config(): if conf.exists('v3 user {0} auth plaintext-key'.format(user)): user_cfg['authPassword'] = conf.return_value('v3 user {0} auth plaintext-key'.format(user)) + # load default value + type = user_cfg['authProtocol'] if conf.exists('v3 user {0} auth type'.format(user)): type = conf.return_value('v3 user {0} auth type'.format(user)) - user_cfg['authProtocol'] = type - user_cfg['authOID'] = OIDs[type] + + # (re-)update with either default value or value from CLI + user_cfg['authProtocol'] = type + user_cfg['authOID'] = OIDs[type] # # v3 user {0} engineid @@ -524,10 +531,14 @@ def get_config(): if conf.exists('v3 user {0} privacy tsm-key'.format(user)): user_cfg['privTsmKey'] = conf.return_value('v3 user {0} privacy tsm-key'.format(user)) + # load default value + type = user_cfg['privProtocol'] if conf.exists('v3 user {0} privacy type'.format(user)): type = conf.return_value('v3 user {0} privacy type'.format(user)) - user_cfg['privProtocol'] = type - user_cfg['privOID'] = OIDs[type] + + # (re-)update with either default value or value from CLI + user_cfg['privProtocol'] = type + user_cfg['privOID'] = OIDs[type] snmp['v3_users'].append(user_cfg) @@ -625,45 +636,52 @@ def verify(snmp): if 'v3_users' in snmp.keys(): for user in snmp['v3_users']: - if user['authPassword'] and user['authMasterKey']: - raise ConfigError('Can not mix "encrypted-key" and "plaintext-key" for user auth') + # + # Group must exist prior to mapping it into a group + # seclevel will be extracted from group + # + error = True + if user['group']: + if 'v3_groups' in snmp.keys(): + for group in snmp['v3_groups']: + if group['name'] == user['group']: + seclevel = group['seclevel'] + error = False - if user['privPassword'] and user['privMasterKey']: - raise ConfigError('Can not mix "encrypted-key" and "plaintext-key" for user privacy') + if error: + raise ConfigError('You must create group "{0}" first'.format(user['group'])) - if user['privPassword'] == '' and user['privMasterKey'] == '': - raise ConfigError('Must specify encrypted-key or plaintext-key for user privacy') + # Depending on the configured security level + # the user has to provide additional info + if seclevel in ('auth', 'priv'): + if user['authPassword'] and user['authMasterKey']: + raise ConfigError('Can not mix "encrypted-key" and "plaintext-key" for user auth') - if user['privMasterKey'] and user['engineID'] == '': - raise ConfigError('Can not have "encrypted-key" without engineid') + if (not user['authPassword'] and not user['authMasterKey']): + raise ConfigError('Must specify encrypted-key or plaintext-key for user auth') - if user['authPassword'] == '' and user['authMasterKey'] == '' and user['privTsmKey'] == '': - raise ConfigError('Must specify auth or tsm-key for user auth') + # seclevel 'priv' is more restrictive + if seclevel in ('priv'): + if user['privPassword'] and user['privMasterKey']: + raise ConfigError('Can not mix "encrypted-key" and "plaintext-key" for user privacy') - if user['privProtocol'] == '': - raise ConfigError('Must specify privacy type') + if user['privPassword'] == '' and user['privMasterKey'] == '': + raise ConfigError('Must specify encrypted-key or plaintext-key for user privacy') - if user['mode'] == '': - raise ConfigError('Must specify user mode ro/rw') + if user['privMasterKey'] and user['engineID'] == '': + raise ConfigError('Can not have "encrypted-key" without engineid') - if user['privTsmKey']: - if not tsmKeyPattern.match(snmp['v3_tsm_key']): - if not os.path.isfile('/etc/snmp/tls/certs/' + snmp['v3_tsm_key']): - if not os.path.isfile('/config/snmp/tls/certs/' + snmp['v3_tsm_key']): - raise ConfigError('User TSM key must be fingerprint or filename in "/config/snmp/tls/certs/" folder') + if user['authPassword'] == '' and user['authMasterKey'] == '' and user['privTsmKey'] == '': + raise ConfigError('Must specify auth or tsm-key for user auth') - if user['group']: - # - # Group must exist prior to mapping it into a group - # - error = True - if 'v3_groups' in snmp.keys(): - for group in snmp['v3_groups']: - if group['name'] == user['group']: - error = False - if error: - raise ConfigError('You must create group "{0}" first'.format(user['group'])) + if user['mode'] == '': + raise ConfigError('Must specify user mode ro/rw') + if user['privTsmKey']: + if not tsmKeyPattern.match(snmp['v3_tsm_key']): + if not os.path.isfile('/etc/snmp/tls/certs/' + snmp['v3_tsm_key']): + if not os.path.isfile('/config/snmp/tls/certs/' + snmp['v3_tsm_key']): + raise ConfigError('User TSM key must be fingerprint or filename in "/config/snmp/tls/certs/" folder') if 'v3_views' in snmp.keys(): for view in snmp['v3_views']: @@ -750,13 +768,16 @@ def apply(snmp): # plaintext password inside the configuration was replaced by # the encrypted one which can be found in 'config_file_user' with open(config_file_user, 'r') as f: + engineID = '' for line in f: - # we are only interested in the user database + if line.startswith('oldEngineID'): + string = line.split(' ') + engineID = string[1] + if line.startswith('usmUser'): string = line.split(' ') cfg = { 'user': string[4].replace(r'"', ''), - 'engineID': string[3], 'auth_pw': string[8], 'priv_pw': string[10] } @@ -767,7 +788,7 @@ def apply(snmp): # Now update the running configuration # # Currently when executing os.system() the environment does not have the vyos_libexec_dir variable set, see T685 - os.system('vyos_libexec_dir=/usr/libexec/vyos /opt/vyatta/sbin/my_set service snmp v3 user "{0}" engineid {1} > /dev/null'.format(cfg['user'], cfg['engineID'])) + os.system('vyos_libexec_dir=/usr/libexec/vyos /opt/vyatta/sbin/my_set service snmp v3 user "{0}" engineid {1} > /dev/null'.format(cfg['user'], engineID)) os.system('vyos_libexec_dir=/usr/libexec/vyos /opt/vyatta/sbin/my_set service snmp v3 user "{0}" auth encrypted-key {1} > /dev/null'.format(cfg['user'], cfg['auth_pw'])) os.system('vyos_libexec_dir=/usr/libexec/vyos /opt/vyatta/sbin/my_set service snmp v3 user "{0}" privacy encrypted-key {1} > /dev/null'.format(cfg['user'], cfg['priv_pw'])) os.system('vyos_libexec_dir=/usr/libexec/vyos /opt/vyatta/sbin/my_delete service snmp v3 user "{0}" auth plaintext-key > /dev/null'.format(cfg['user'])) diff --git a/src/helpers/commands-pipe.py b/src/helpers/commands-pipe.py index 3ffc64545..1120bb09e 100755 --- a/src/helpers/commands-pipe.py +++ b/src/helpers/commands-pipe.py @@ -8,7 +8,6 @@ from vyos.configtree import ConfigTree signal(SIGPIPE,SIG_DFL) - config_string = sys.stdin.read().strip() if not config_string: @@ -21,7 +20,7 @@ if not config_string: # __root__ is hygienic enough. config_string = "__root__ {{ {0} \n }}".format(config_string) -config_re = re.compile("(set|comment)\s+__root__\s+(.*)") +config_re = re.compile(r'(set|comment)\s+__root__\s+(.*)') config = ConfigTree(config_string) commands = config.to_commands() diff --git a/src/op_mode/cpu_summary.py b/src/op_mode/cpu_summary.py index 3da5835e9..7324c75ad 100755 --- a/src/op_mode/cpu_summary.py +++ b/src/op_mode/cpu_summary.py @@ -18,7 +18,7 @@ cpu_data['cpu_number'] = len(data['processor']) cpu_data['models'] = list(set(data['model name'])) # Strip extra whitespace from CPU model names, /proc/cpuinfo is prone to that -cpu_data['models'] = map(lambda s: re.sub('\s+', ' ', s), cpu_data['models']) +cpu_data['models'] = map(lambda s: re.sub(r'\s+', ' ', s), cpu_data['models']) print("CPU(s): {0}".format(cpu_data['cpu_number'])) print("CPU model(s): {0}".format(",".join(cpu_data['models']))) diff --git a/src/op_mode/dns_forwarding_restart.sh b/src/op_mode/dns_forwarding_restart.sh index 12106fcc1..8e556f2f0 100755 --- a/src/op_mode/dns_forwarding_restart.sh +++ b/src/op_mode/dns_forwarding_restart.sh @@ -1,6 +1,6 @@ #!/bin/sh -if cli-shell-api exists service dns forwarding; then +if cli-shell-api existsEffective service dns forwarding; then echo "Restarting the DNS forwarding service" systemctl restart pdns-recursor else diff --git a/src/tests/test_initial_setup.py b/src/tests/test_initial_setup.py new file mode 100644 index 000000000..023a30723 --- /dev/null +++ b/src/tests/test_initial_setup.py @@ -0,0 +1,103 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2018 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 tempfile +import unittest +from unittest import TestCase, mock + +import vyos.configtree +import vyos.initialsetup as vis + + +class TestHostName(TestCase): + def setUp(self): + with open('tests/data/config.boot.default', 'r') as f: + config_string = f.read() + self.config = vyos.configtree.ConfigTree(config_string) + + def test_set_user_password(self): + vis.set_user_password(self.config, 'vyos', 'vyosvyos') + + # Old password hash from the default config + old_pw = '$6$QxPS.uk6mfo$9QBSo8u1FkH16gMyAVhus6fU3LOzvLR9Z9.82m3tiHFAxTtIkhaZSWssSgzt4v4dGAL8rhVQxTg0oAG9/q11h/' + new_pw = self.config.return_value(["system", "login", "user", "vyos", "authentication", "encrypted-password"]) + + # Just check it changed the hash, don't try to check if hash is good + self.assertNotEqual(old_pw, new_pw) + + def test_disable_user_password(self): + vis.disable_user_password(self.config, 'vyos') + new_pw = self.config.return_value(["system", "login", "user", "vyos", "authentication", "encrypted-password"]) + + self.assertEqual(new_pw, '!') + + def test_set_ssh_key_with_name(self): + test_ssh_key = " ssh-rsa fakedata vyos@vyos " + vis.set_user_ssh_key(self.config, 'vyos', test_ssh_key) + + key_type = self.config.return_value(["system", "login", "user", "vyos", "authentication", "public-keys", "vyos@vyos", "type"]) + key_data = self.config.return_value(["system", "login", "user", "vyos", "authentication", "public-keys", "vyos@vyos", "key"]) + + self.assertEqual(key_type, 'ssh-rsa') + self.assertEqual(key_data, 'fakedata') + self.assertTrue(self.config.is_tag(["system", "login", "user", "vyos", "authentication", "public-keys"])) + + def test_set_ssh_key_without_name(self): + # If key file doesn't include a name, the function will use user name for the key name + + test_ssh_key = " ssh-rsa fakedata " + vis.set_user_ssh_key(self.config, 'vyos', test_ssh_key) + + key_type = self.config.return_value(["system", "login", "user", "vyos", "authentication", "public-keys", "vyos", "type"]) + key_data = self.config.return_value(["system", "login", "user", "vyos", "authentication", "public-keys", "vyos", "key"]) + + self.assertEqual(key_type, 'ssh-rsa') + self.assertEqual(key_data, 'fakedata') + self.assertTrue(self.config.is_tag(["system", "login", "user", "vyos", "authentication", "public-keys"])) + + def test_create_user(self): + vis.create_user(self.config, 'jrandomhacker', password='qwerty', key=" ssh-rsa fakedata jrandomhacker@foovax ") + + self.assertTrue(self.config.exists(["system", "login", "user", "jrandomhacker"])) + self.assertTrue(self.config.exists(["system", "login", "user", "jrandomhacker", "authentication", "public-keys", "jrandomhacker@foovax"])) + self.assertTrue(self.config.exists(["system", "login", "user", "jrandomhacker", "authentication", "encrypted-password"])) + self.assertEqual(self.config.return_value(["system", "login", "user", "jrandomhacker", "level"]), "admin") + + def test_set_hostname(self): + vis.set_host_name(self.config, "vyos-test") + + self.assertEqual(self.config.return_value(["system", "host-name"]), "vyos-test") + + def test_set_name_servers(self): + vis.set_name_servers(self.config, ["192.0.2.10", "203.0.113.20"]) + servers = self.config.return_values(["system", "name-server"]) + + self.assertIn("192.0.2.10", servers) + self.assertIn("203.0.113.20", servers) + + def test_set_gateway(self): + vis.set_default_gateway(self.config, '192.0.2.1') + + self.assertTrue(self.config.exists(['protocols', 'static', 'route', '0.0.0.0/0', 'next-hop', '192.0.2.1'])) + self.assertTrue(self.config.is_tag(['protocols', 'static', 'route', '0.0.0.0/0', 'next-hop'])) + self.assertTrue(self.config.is_tag(['protocols', 'static', 'route'])) + +if __name__ == "__main__": + unittest.main() + diff --git a/src/utils/initial-setup b/src/utils/initial-setup new file mode 100644 index 000000000..37fc45751 --- /dev/null +++ b/src/utils/initial-setup @@ -0,0 +1,40 @@ +#!/usr/bin/env python3 + +import argparse + +import vyos.configtree + + +parser = argparse.ArgumentParser() + +parser.add_argument("--ssh", help="Enable SSH", action="store_true") +parser.add_argument("--ssh-port", help="SSH port", type=int, action="store", default=22) + +parser.add_argument("--intf-address", help="Set interface address", type=str, action="append") + +parser.add_argument("config_file", help="Configuration file to modify", type=str) + +args = parser.parse_args() + +# Load the config file +with open(args.config_file, 'r') as f: + config_file = f.read() + +config = vyos.configtree.ConfigTree(config_file) + + +# Interface names and addresses are comma-separated, +# we need to split them +intf_addrs = list(map(lambda s: s.split(","), args.intf_address)) + +# Enable SSH, if requested +if args.ssh: + config.set(["service", "ssh", "port"], value=str(args.ssh_port)) + +# Assign addresses to interfaces +if intf_addrs: + for a in intf_addrs: + config.set(["interfaces", "ethernet", a[0], "address"], value=a[1]) + config.set_tag(["interfaces", "ethernet"]) + +print( config.to_string() ) diff --git a/src/validators/numeric b/src/validators/numeric index 58a4fac38..ffe84a234 100755 --- a/src/validators/numeric +++ b/src/validators/numeric @@ -25,7 +25,8 @@ parser = argparse.ArgumentParser() parser.add_argument("-f", "--float", action="store_true", help="Accept floating point values") group = parser.add_mutually_exclusive_group() group.add_argument("-r", "--range", type=str, help="Check if the number is within range (inclusive), example: 1024-65535") -group.add_argument("-n", "--non-negative", action="store_true", help="") +group.add_argument("-n", "--non-negative", action="store_true", help="Check if the number is non-negative (>= 0)") +group.add_argument("-p", "--positive", action="store_true", help="Check if the number is positive (> 0)") parser.add_argument("number", type=str, help="Number to validate") args = parser.parse_args() @@ -60,3 +61,8 @@ elif args.non_negative: if number < 0: print("Number should be non-negative", file=sys.stderr) sys.exit(1) +elif args.positive: + if number <= 0: + print("Number should be positive", file=sys.stderr) + sys.exit(1) + diff --git a/tests/data/config.boot.default b/tests/data/config.boot.default new file mode 100644 index 000000000..0a75716b8 --- /dev/null +++ b/tests/data/config.boot.default @@ -0,0 +1,40 @@ +system { + host-name vyos + login { + user vyos { + authentication { + encrypted-password $6$QxPS.uk6mfo$9QBSo8u1FkH16gMyAVhus6fU3LOzvLR9Z9.82m3tiHFAxTtIkhaZSWssSgzt4v4dGAL8rhVQxTg0oAG9/q11h/ + plaintext-password "" + } + level admin + } + } + syslog { + global { + facility all { + level notice + } + facility protocols { + level debug + } + } + } + ntp { + server "0.pool.ntp.org" + server "1.pool.ntp.org" + server "2.pool.ntp.org" + } + console { + device ttyS0 { + speed 9600 + } + } + config-management { + commit-revisions 100 + } +} + +interfaces { + loopback lo { + } +} |