summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Makefile5
-rw-r--r--debian/control4
-rw-r--r--interface-definitions/beep-on-boot.xml21
-rw-r--r--interface-definitions/snmp.xml4
-rw-r--r--interface-definitions/ssh.xml16
-rw-r--r--python/vyos/authutils.py43
-rw-r--r--python/vyos/initialsetup.py72
-rw-r--r--python/vyos/util.py51
-rwxr-xr-xsrc/conf_mode/beep_if_fully_booted.py42
-rwxr-xr-xsrc/conf_mode/snmp.py105
-rwxr-xr-xsrc/helpers/commands-pipe.py3
-rwxr-xr-xsrc/op_mode/cpu_summary.py2
-rwxr-xr-xsrc/op_mode/dns_forwarding_restart.sh2
-rw-r--r--src/tests/test_initial_setup.py103
-rw-r--r--src/utils/initial-setup40
-rwxr-xr-xsrc/validators/numeric8
-rw-r--r--tests/data/config.boot.default40
17 files changed, 510 insertions, 51 deletions
diff --git a/Makefile b/Makefile
index 84cc8fa90..7447275ca 100644
--- a/Makefile
+++ b/Makefile
@@ -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 {
+ }
+}