summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorIgor Galić <me+github@igalic.co>2019-11-26 17:44:21 +0100
committerChad Smith <chad.smith@canonical.com>2019-11-26 09:44:21 -0700
commitb6055c40189afba323986059434b8d8adc85bba3 (patch)
tree8dcfe0b90986f11a056fcd03403e8f3495dd679d
parent250a3f92473feeb2689f3a214e8f1b79fa419334 (diff)
downloadvyos-cloud-init-b6055c40189afba323986059434b8d8adc85bba3.tar.gz
vyos-cloud-init-b6055c40189afba323986059434b8d8adc85bba3.zip
set_passwords: support for FreeBSD (#46)
Allow setting of user passwords on FreeBSD The www/chpasswd utility which we depended on for FreeBSD installations does *not* do the same thing as the equally named Linux utility. For FreeBSD, we now use the pw(8) utility (which can only process one user at a time) Additionally, we abstract expire passwd into a function, and override it in the FreeBSD distro class. Co-Authored-By: Chad Smith <chad.smith@canonical.com>
-rwxr-xr-xcloudinit/config/cc_set_passwords.py21
-rw-r--r--cloudinit/config/tests/test_set_passwords.py42
-rw-r--r--cloudinit/distros/__init__.py7
-rw-r--r--cloudinit/distros/freebsd.py7
-rw-r--r--tests/unittests/test_distros/test_generic.py18
-rwxr-xr-xtools/build-on-freebsd1
6 files changed, 89 insertions, 7 deletions
diff --git a/cloudinit/config/cc_set_passwords.py b/cloudinit/config/cc_set_passwords.py
index 1379428d..c3c5b0ff 100755
--- a/cloudinit/config/cc_set_passwords.py
+++ b/cloudinit/config/cc_set_passwords.py
@@ -179,20 +179,21 @@ def handle(_name, cfg, cloud, log, args):
for line in plist:
u, p = line.split(':', 1)
if prog.match(p) is not None and ":" not in p:
- hashed_plist_in.append("%s:%s" % (u, p))
+ hashed_plist_in.append(line)
hashed_users.append(u)
else:
+ # in this else branch, we potentially change the password
+ # hence, a deviation from .append(line)
if p == "R" or p == "RANDOM":
p = rand_user_password()
randlist.append("%s:%s" % (u, p))
plist_in.append("%s:%s" % (u, p))
users.append(u)
-
ch_in = '\n'.join(plist_in) + '\n'
if users:
try:
log.debug("Changing password for %s:", users)
- util.subp(['chpasswd'], ch_in)
+ chpasswd(cloud.distro, ch_in)
except Exception as e:
errors.append(e)
util.logexc(
@@ -202,7 +203,7 @@ def handle(_name, cfg, cloud, log, args):
if hashed_users:
try:
log.debug("Setting hashed password for %s:", hashed_users)
- util.subp(['chpasswd', '-e'], hashed_ch_in)
+ chpasswd(cloud.distro, hashed_ch_in, hashed=True)
except Exception as e:
errors.append(e)
util.logexc(
@@ -218,7 +219,7 @@ def handle(_name, cfg, cloud, log, args):
expired_users = []
for u in users:
try:
- util.subp(['passwd', '--expire', u])
+ cloud.distro.expire_passwd(u)
expired_users.append(u)
except Exception as e:
errors.append(e)
@@ -238,4 +239,14 @@ def handle(_name, cfg, cloud, log, args):
def rand_user_password(pwlen=9):
return util.rand_str(pwlen, select_from=PW_SET)
+
+def chpasswd(distro, plist_in, hashed=False):
+ if util.is_FreeBSD():
+ for pentry in plist_in.splitlines():
+ u, p = pentry.split(":")
+ distro.set_passwd(u, p, hashed=hashed)
+ else:
+ cmd = ['chpasswd'] + (['-e'] if hashed else [])
+ util.subp(cmd, plist_in)
+
# vi: ts=4 expandtab
diff --git a/cloudinit/config/tests/test_set_passwords.py b/cloudinit/config/tests/test_set_passwords.py
index a2ea5ec4..639fb9ea 100644
--- a/cloudinit/config/tests/test_set_passwords.py
+++ b/cloudinit/config/tests/test_set_passwords.py
@@ -74,7 +74,7 @@ class TestSetPasswordsHandle(CiTestCase):
with_logs = True
- def test_handle_on_empty_config(self):
+ def test_handle_on_empty_config(self, *args):
"""handle logs that no password has changed when config is empty."""
cloud = self.tmp_cloud(distro='ubuntu')
setpass.handle(
@@ -108,4 +108,44 @@ class TestSetPasswordsHandle(CiTestCase):
'\n'.join(valid_hashed_pwds) + '\n')],
m_subp.call_args_list)
+ @mock.patch(MODPATH + "util.is_FreeBSD")
+ @mock.patch(MODPATH + "util.subp")
+ def test_freebsd_calls_custom_pw_cmds_to_set_and_expire_passwords(
+ self, m_subp, m_is_freebsd):
+ """FreeBSD calls custom pw commands instead of chpasswd and passwd"""
+ m_is_freebsd.return_value = True
+ cloud = self.tmp_cloud(distro='freebsd')
+ valid_pwds = ['ubuntu:passw0rd']
+ cfg = {'chpasswd': {'list': valid_pwds}}
+ setpass.handle(
+ 'IGNORED', cfg=cfg, cloud=cloud, log=self.logger, args=[])
+ self.assertEqual([
+ mock.call(['pw', 'usermod', 'ubuntu', '-h', '0'], data='passw0rd',
+ logstring="chpasswd for ubuntu"),
+ mock.call(['pw', 'usermod', 'ubuntu', '-p', '01-Jan-1970'])],
+ m_subp.call_args_list)
+
+ @mock.patch(MODPATH + "util.is_FreeBSD")
+ @mock.patch(MODPATH + "util.subp")
+ def test_handle_on_chpasswd_list_creates_random_passwords(self, m_subp,
+ m_is_freebsd):
+ """handle parses command set random passwords."""
+ m_is_freebsd.return_value = False
+ cloud = self.tmp_cloud(distro='ubuntu')
+ valid_random_pwds = [
+ 'root:R',
+ 'ubuntu:RANDOM']
+ cfg = {'chpasswd': {'expire': 'false', 'list': valid_random_pwds}}
+ with mock.patch(MODPATH + 'util.subp') as m_subp:
+ setpass.handle(
+ 'IGNORED', cfg=cfg, cloud=cloud, log=self.logger, args=[])
+ self.assertIn(
+ 'DEBUG: Handling input for chpasswd as list.',
+ self.logs.getvalue())
+ self.assertNotEqual(
+ [mock.call(['chpasswd'],
+ '\n'.join(valid_random_pwds) + '\n')],
+ m_subp.call_args_list)
+
+
# vi: ts=4 expandtab
diff --git a/cloudinit/distros/__init__.py b/cloudinit/distros/__init__.py
index 00bdee3d..2ec79577 100644
--- a/cloudinit/distros/__init__.py
+++ b/cloudinit/distros/__init__.py
@@ -591,6 +591,13 @@ class Distro(object):
util.logexc(LOG, 'Failed to disable password for user %s', name)
raise e
+ def expire_passwd(self, user):
+ try:
+ util.subp(['passwd', '--expire', user])
+ except Exception as e:
+ util.logexc(LOG, "Failed to set 'expire' for %s", user)
+ raise e
+
def set_passwd(self, user, passwd, hashed=False):
pass_string = '%s:%s' % (user, passwd)
cmd = ['chpasswd']
diff --git a/cloudinit/distros/freebsd.py b/cloudinit/distros/freebsd.py
index c55f8990..8e5ae96c 100644
--- a/cloudinit/distros/freebsd.py
+++ b/cloudinit/distros/freebsd.py
@@ -234,6 +234,13 @@ class Distro(distros.Distro):
if passwd_val is not None:
self.set_passwd(name, passwd_val, hashed=True)
+ def expire_passwd(self, user):
+ try:
+ util.subp(['pw', 'usermod', user, '-p', '01-Jan-1970'])
+ except Exception as e:
+ util.logexc(LOG, "Failed to set pw expiration for %s", user)
+ raise e
+
def set_passwd(self, user, passwd, hashed=False):
if hashed:
hash_opt = "-H"
diff --git a/tests/unittests/test_distros/test_generic.py b/tests/unittests/test_distros/test_generic.py
index 791fe612..7e0da4f2 100644
--- a/tests/unittests/test_distros/test_generic.py
+++ b/tests/unittests/test_distros/test_generic.py
@@ -244,5 +244,23 @@ class TestGenericDistro(helpers.FilesystemMockingTestCase):
with self.assertRaises(NotImplementedError):
d.get_locale()
+ def test_expire_passwd_uses_chpasswd(self):
+ """Test ubuntu.expire_passwd uses the passwd command."""
+ for d_name in ("ubuntu", "rhel"):
+ cls = distros.fetch(d_name)
+ d = cls(d_name, {}, None)
+ with mock.patch("cloudinit.util.subp") as m_subp:
+ d.expire_passwd("myuser")
+ m_subp.assert_called_once_with(["passwd", "--expire", "myuser"])
+
+ def test_expire_passwd_freebsd_uses_pw_command(self):
+ """Test FreeBSD.expire_passwd uses the pw command."""
+ cls = distros.fetch("freebsd")
+ d = cls("freebsd", {}, None)
+ with mock.patch("cloudinit.util.subp") as m_subp:
+ d.expire_passwd("myuser")
+ m_subp.assert_called_once_with(
+ ["pw", "usermod", "myuser", "-p", "01-Jan-1970"])
+
# vi: ts=4 expandtab
diff --git a/tools/build-on-freebsd b/tools/build-on-freebsd
index 8ae64567..876368a9 100755
--- a/tools/build-on-freebsd
+++ b/tools/build-on-freebsd
@@ -18,7 +18,6 @@ py_prefix=$(${PYTHON} -c 'import sys; print("py%d%d" % (sys.version_info.major,
depschecked=/tmp/c-i.dependencieschecked
pkgs="
bash
- chpasswd
dmidecode
e2fsprogs
$py_prefix-Jinja2