diff options
-rw-r--r-- | python/vyos/authutils.py | 43 | ||||
-rw-r--r-- | python/vyos/initialsetup.py | 72 | ||||
-rw-r--r-- | src/tests/test_initial_setup.py | 103 | ||||
-rw-r--r-- | tests/data/config.boot.default | 40 |
4 files changed, 258 insertions, 0 deletions
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/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/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 { + } +} |