From 5a0bee770fe80573e8efad9ca97233079adebd78 Mon Sep 17 00:00:00 2001 From: Sergio Lystopad Date: Fri, 17 Feb 2017 22:50:32 +0200 Subject: doc: Fix configuration example for cc_set_passwords module. The documentation indicated chpasswd/list should be a list when the code only accepts a string. LP: #1665773 --- cloudinit/config/cc_set_passwords.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) (limited to 'cloudinit/config/cc_set_passwords.py') diff --git a/cloudinit/config/cc_set_passwords.py b/cloudinit/config/cc_set_passwords.py index cf1f59ec..af6704c6 100755 --- a/cloudinit/config/cc_set_passwords.py +++ b/cloudinit/config/cc_set_passwords.py @@ -45,11 +45,11 @@ enabled, disabled, or left to system defaults using ``ssh_pwauth``. expire: chpasswd: - list: - - user1:password1 - - user2:Random - - user3:password3 - - user4:R + list: | + user1:password1 + user2:Random + user3:password3 + user4:R """ import sys -- cgit v1.2.3 From 7f2b51054a5defec4c04fc40026bda7dd29f69fe Mon Sep 17 00:00:00 2001 From: Sergio Lystopad Date: Mon, 20 Feb 2017 16:45:33 +0200 Subject: Support chpasswd/list being a list in addition to a string. cc_set_passwords previously supported 'list' as a multiline string: chpasswd: list: | user:pass1 user015:R This patch adds support for user/pairs as a list: chpasswd: list: - user:pass1 - user015:R LP: #1665694 --- cloudinit/config/cc_set_passwords.py | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) (limited to 'cloudinit/config/cc_set_passwords.py') diff --git a/cloudinit/config/cc_set_passwords.py b/cloudinit/config/cc_set_passwords.py index af6704c6..fa343a7a 100755 --- a/cloudinit/config/cc_set_passwords.py +++ b/cloudinit/config/cc_set_passwords.py @@ -50,6 +50,16 @@ enabled, disabled, or left to system defaults using ``ssh_pwauth``. user2:Random user3:password3 user4:R + + ## + # or as yaml list + ## + chpasswd: + list: + - user1:password1 + - user2:Random + - user3:password3 + - user4:R """ import sys @@ -79,7 +89,15 @@ def handle(_name, cfg, cloud, log, args): if 'chpasswd' in cfg: chfg = cfg['chpasswd'] - plist = util.get_cfg_option_str(chfg, 'list', plist) + if isinstance(chfg['list'], list): + log.debug("Handling input for chpasswd as list.") + plist = util.get_cfg_option_list(chfg, 'list', plist) + else: + log.debug("Handling input for chpasswd as multiline string.") + plist = util.get_cfg_option_str(chfg, 'list', plist) + if plist: + plist = plist.spitlines() + expire = util.get_cfg_option_bool(chfg, 'expire', expire) if not plist and password: @@ -95,7 +113,7 @@ def handle(_name, cfg, cloud, log, args): plist_in = [] randlist = [] users = [] - for line in plist.splitlines(): + for line in plist: u, p = line.split(':', 1) if p == "R" or p == "RANDOM": p = rand_user_password() -- cgit v1.2.3 From 021ed9c960484dcb45941d48139ec86c2ce1f248 Mon Sep 17 00:00:00 2001 From: Scott Moser Date: Fri, 10 Mar 2017 10:18:18 -0500 Subject: fix regression when no chpasswd/list was provided. This regression was caused by my rework of Sergio's branch. The change now still works when there is no chpasswd/list provided. --- cloudinit/config/cc_set_passwords.py | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) (limited to 'cloudinit/config/cc_set_passwords.py') diff --git a/cloudinit/config/cc_set_passwords.py b/cloudinit/config/cc_set_passwords.py index fa343a7a..f36f7745 100755 --- a/cloudinit/config/cc_set_passwords.py +++ b/cloudinit/config/cc_set_passwords.py @@ -89,14 +89,15 @@ def handle(_name, cfg, cloud, log, args): if 'chpasswd' in cfg: chfg = cfg['chpasswd'] - if isinstance(chfg['list'], list): - log.debug("Handling input for chpasswd as list.") - plist = util.get_cfg_option_list(chfg, 'list', plist) - else: - log.debug("Handling input for chpasswd as multiline string.") - plist = util.get_cfg_option_str(chfg, 'list', plist) - if plist: - plist = plist.spitlines() + if 'list' in chfg and chfg['list']: + if isinstance(chfg['list'], list): + log.debug("Handling input for chpasswd as list.") + plist = util.get_cfg_option_list(chfg, 'list', plist) + else: + log.debug("Handling input for chpasswd as multiline string.") + plist = util.get_cfg_option_str(chfg, 'list', plist) + if plist: + plist = plist.splitlines() expire = util.get_cfg_option_bool(chfg, 'expire', expire) -- cgit v1.2.3 From 75a421521153cacd01f6e9df83096f82475e8b08 Mon Sep 17 00:00:00 2001 From: Scott Moser Date: Fri, 10 Mar 2017 12:39:30 -0500 Subject: Further fix regression to support 'password' for default user. The adjusted change did not support #cloud-config password: passw0rd This correctly fixes that regression. --- cloudinit/config/cc_set_passwords.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'cloudinit/config/cc_set_passwords.py') diff --git a/cloudinit/config/cc_set_passwords.py b/cloudinit/config/cc_set_passwords.py index f36f7745..16117048 100755 --- a/cloudinit/config/cc_set_passwords.py +++ b/cloudinit/config/cc_set_passwords.py @@ -105,7 +105,7 @@ def handle(_name, cfg, cloud, log, args): (users, _groups) = ug_util.normalize_users_groups(cfg, cloud.distro) (user, _user_config) = ug_util.extract_default(users) if user: - plist = "%s:%s" % (user, password) + plist = ["%s:%s" % (user, password)] else: log.warn("No default or defined user to change password for.") -- cgit v1.2.3 From 41950e902f5dd6cb3118280d3d27409812702e41 Mon Sep 17 00:00:00 2001 From: Joshua Powers Date: Tue, 14 Mar 2017 15:28:08 -0600 Subject: test: Adding integratiron test for password as list This adds an integration test for setting passwords when given as a list rather than a string. This also updates the docs and tests so that Random is now RANDOM as is correct. --- cloudinit/config/cc_set_passwords.py | 4 +- .../configs/modules/set_password_list.yaml | 20 +++++---- .../configs/modules/set_password_list_string.yaml | 37 +++++++++++++++ tests/cloud_tests/testcases/base.py | 52 ++++++++++++++++++++++ .../testcases/modules/set_password_list.py | 20 ++------- .../testcases/modules/set_password_list_string.py | 11 +++++ 6 files changed, 117 insertions(+), 27 deletions(-) create mode 100644 tests/cloud_tests/configs/modules/set_password_list_string.yaml create mode 100644 tests/cloud_tests/testcases/modules/set_password_list_string.py (limited to 'cloudinit/config/cc_set_passwords.py') diff --git a/cloudinit/config/cc_set_passwords.py b/cloudinit/config/cc_set_passwords.py index 16117048..8440e593 100755 --- a/cloudinit/config/cc_set_passwords.py +++ b/cloudinit/config/cc_set_passwords.py @@ -47,7 +47,7 @@ enabled, disabled, or left to system defaults using ``ssh_pwauth``. chpasswd: list: | user1:password1 - user2:Random + user2:RANDOM user3:password3 user4:R @@ -57,7 +57,7 @@ enabled, disabled, or left to system defaults using ``ssh_pwauth``. chpasswd: list: - user1:password1 - - user2:Random + - user2:RANDOM - user3:password3 - user4:R """ diff --git a/tests/cloud_tests/configs/modules/set_password_list.yaml b/tests/cloud_tests/configs/modules/set_password_list.yaml index 36129047..a1eadd75 100644 --- a/tests/cloud_tests/configs/modules/set_password_list.yaml +++ b/tests/cloud_tests/configs/modules/set_password_list.yaml @@ -6,22 +6,26 @@ cloud_config: | ssh_pwauth: yes users: - name: tom - password: $1$xyz$sPMsLNmf66Ohl.ol6JvzE. + # md5 gotomgo + passwd: "$1$S7$tT1BEDIYrczeryDQJfdPe0" lock_passwd: false - name: dick - password: $1$xyz$sPMsLNmf66Ohl.ol6JvzE. + # md5 gocubsgo + passwd: "$1$ssisyfpf$YqvuJLfrrW6Cg/l53Pi1n1" lock_passwd: false - name: harry - password: $1$xyz$sPMsLNmf66Ohl.ol6JvzE. + # sha512 goharrygo + passwd: "$6$LF$9Z2p6rWK6TNC1DC6393ec0As.18KRAvKDbfsGJEdWN3sRQRwpdfoh37EQ3yUh69tP4GSrGW5XKHxMLiKowJgm/" lock_passwd: false - name: jane - password: $1$xyz$sPMsLNmf66Ohl.ol6JvzE. + # sha256 gojanego + passwd: "$5$iW$XsxmWCdpwIW8Yhv.Jn/R3uk6A4UaicfW5Xp7C9p9pg." lock_passwd: false chpasswd: - list: | - tom:mypassword123! - dick:R - harry:Random + list: + - tom:mypassword123! + - dick:RANDOM + - harry:RANDOM collect_scripts: shadow: | #!/bin/bash diff --git a/tests/cloud_tests/configs/modules/set_password_list_string.yaml b/tests/cloud_tests/configs/modules/set_password_list_string.yaml new file mode 100644 index 00000000..cbb71bee --- /dev/null +++ b/tests/cloud_tests/configs/modules/set_password_list_string.yaml @@ -0,0 +1,37 @@ +# +# Set password of list of users as a string +# +cloud_config: | + #cloud-config + ssh_pwauth: yes + users: + - name: tom + # md5 gotomgo + passwd: "$1$S7$tT1BEDIYrczeryDQJfdPe0" + lock_passwd: false + - name: dick + # md5 gocubsgo + passwd: "$1$ssisyfpf$YqvuJLfrrW6Cg/l53Pi1n1" + lock_passwd: false + - name: harry + # sha512 goharrygo + passwd: "$6$LF$9Z2p6rWK6TNC1DC6393ec0As.18KRAvKDbfsGJEdWN3sRQRwpdfoh37EQ3yUh69tP4GSrGW5XKHxMLiKowJgm/" + lock_passwd: false + - name: jane + # sha256 gojanego + passwd: "$5$iW$XsxmWCdpwIW8Yhv.Jn/R3uk6A4UaicfW5Xp7C9p9pg." + lock_passwd: false + chpasswd: + list: | + tom:mypassword123! + dick:RANDOM + harry:RANDOM +collect_scripts: + shadow: | + #!/bin/bash + cat /etc/shadow + sshd_config: | + #!/bin/bash + grep '^PasswordAuth' /etc/ssh/sshd_config + +# vi: ts=4 expandtab diff --git a/tests/cloud_tests/testcases/base.py b/tests/cloud_tests/testcases/base.py index 5395b9a3..51ce2b41 100644 --- a/tests/cloud_tests/testcases/base.py +++ b/tests/cloud_tests/testcases/base.py @@ -2,6 +2,7 @@ from cloudinit import util as c_util +import crypt import json import unittest @@ -14,6 +15,9 @@ class CloudTestCase(unittest.TestCase): conf = None _cloud_config = None + def shortDescription(self): + return None + @property def cloud_config(self): """ @@ -78,4 +82,52 @@ class CloudTestCase(unittest.TestCase): result = self.get_status_data(self.get_data_file('result.json')) self.assertEqual(len(result['errors']), 0) + +class PasswordListTest(CloudTestCase): + def test_shadow_passwords(self): + shadow = self.get_data_file('shadow') + users = {} + dupes = [] + for line in shadow.splitlines(): + user, encpw = line.split(":")[0:2] + if user in users: + dupes.append(user) + users[user] = encpw + + jane_enc = "$5$iW$XsxmWCdpwIW8Yhv.Jn/R3uk6A4UaicfW5Xp7C9p9pg." + self.assertEqual([], dupes) + self.assertEqual(jane_enc, users['jane']) + + # shadow entry is $N$salt$, so we encrypt with the same format + # and salt and expect the result. + tom = "mypassword123!" + fmtsalt = users['tom'][0:users['tom'].rfind("$") + 1] + tom_enc = crypt.crypt(tom, fmtsalt) + self.assertEqual(tom_enc, users['tom']) + + harry_enc = ("$6$LF$9Z2p6rWK6TNC1DC6393ec0As.18KRAvKDbfsG" + "JEdWN3sRQRwpdfoh37EQ3yUh69tP4GSrGW5XKHxMLiKowJgm/") + dick_enc = "$1$ssisyfpf$YqvuJLfrrW6Cg/l53Pi1n1" + + # these should have been changed to random values. + self.assertNotEqual(harry_enc, users['harry']) + self.assertTrue(users['harry'].startswith("$")) + self.assertNotEqual(dick_enc, users['dick']) + self.assertTrue(users['dick'].startswith("$")) + + self.assertNotEqual(users['harry'], users['dick']) + + def test_shadow_expected_users(self): + """Test every tom, dick, and harry user in shadow""" + out = self.get_data_file('shadow') + self.assertIn('tom:', out) + self.assertIn('dick:', out) + self.assertIn('harry:', out) + self.assertIn('jane:', out) + + def test_sshd_config(self): + """Test sshd config allows passwords""" + out = self.get_data_file('sshd_config') + self.assertIn('PasswordAuthentication yes', out) + # vi: ts=4 expandtab diff --git a/tests/cloud_tests/testcases/modules/set_password_list.py b/tests/cloud_tests/testcases/modules/set_password_list.py index b764362f..6819d259 100644 --- a/tests/cloud_tests/testcases/modules/set_password_list.py +++ b/tests/cloud_tests/testcases/modules/set_password_list.py @@ -4,22 +4,8 @@ from tests.cloud_tests.testcases import base -class TestPasswordList(base.CloudTestCase): - """Test password module""" - - # TODO: Verify dick and harry passwords are random - # TODO: Verify tom's password was changed - - def test_shadow(self): - """Test every tom, dick, and harry user in shadow""" - out = self.get_data_file('shadow') - self.assertIn('tom:', out) - self.assertIn('dick:', out) - self.assertIn('harry:', out) - - def test_sshd_config(self): - """Test sshd config allows passwords""" - out = self.get_data_file('sshd_config') - self.assertIn('PasswordAuthentication yes', out) +class TestPasswordList(base.PasswordListTest, base.CloudTestCase): + """Test password setting via list in chpasswd/list""" + __test__ = True # vi: ts=4 expandtab diff --git a/tests/cloud_tests/testcases/modules/set_password_list_string.py b/tests/cloud_tests/testcases/modules/set_password_list_string.py new file mode 100644 index 00000000..2c34fada --- /dev/null +++ b/tests/cloud_tests/testcases/modules/set_password_list_string.py @@ -0,0 +1,11 @@ +# This file is part of cloud-init. See LICENSE file for license information. + +"""cloud-init Integration Test Verify Script""" +from tests.cloud_tests.testcases import base + + +class TestPasswordListString(base.PasswordListTest, base.CloudTestCase): + """Test password setting via string in chpasswd/list""" + __test__ = True + +# vi: ts=4 expandtab -- cgit v1.2.3 From 21632972df034c200578e1fbc121a07f20bb8774 Mon Sep 17 00:00:00 2001 From: "Tore S. Lonoy" Date: Fri, 4 Nov 2016 11:38:31 +0100 Subject: Add support for setting hashed passwords This change will add support for hashed passwords in cc_set_passwords. It checks if a password is a hash with by checking that it matches in fairly safe way, and also that the password does not have a ":" in it. chpasswd needs to know if the password is hashed or not, so two lists is created so chpasswd is feed with the correct one. LP: #1570325 --- cloudinit/config/cc_set_passwords.py | 48 ++++++++++++++++------ doc/examples/cloud-config.txt | 9 +++- .../configs/modules/set_password_list.yaml | 3 ++ .../configs/modules/set_password_list_string.yaml | 3 ++ tests/cloud_tests/testcases/base.py | 4 ++ 5 files changed, 53 insertions(+), 14 deletions(-) (limited to 'cloudinit/config/cc_set_passwords.py') diff --git a/cloudinit/config/cc_set_passwords.py b/cloudinit/config/cc_set_passwords.py index 8440e593..eb0bdab0 100755 --- a/cloudinit/config/cc_set_passwords.py +++ b/cloudinit/config/cc_set_passwords.py @@ -23,7 +23,8 @@ If the ``list`` key is provided, a list of ``username:password`` pairs can be specified. The usernames specified must already exist on the system, or have been created using the ``cc_users_groups`` module. A password can be randomly generated using -``username:RANDOM`` or ``username:R``. Password ssh authentication can be +``username:RANDOM`` or ``username:R``. A hashed password can be specified +using ``username:$6$salt$hash``. Password ssh authentication can be enabled, disabled, or left to system defaults using ``ssh_pwauth``. .. note:: @@ -60,8 +61,10 @@ enabled, disabled, or left to system defaults using ``ssh_pwauth``. - user2:RANDOM - user3:password3 - user4:R + - user4:$6$rL..$ej... """ +import re import sys from cloudinit.distros import ug_util @@ -112,24 +115,43 @@ def handle(_name, cfg, cloud, log, args): errors = [] if plist: plist_in = [] + hashed_plist_in = [] + hashed_users = [] randlist = [] users = [] + prog = re.compile(r'\$[1,2a,2y,5,6](\$.+){2}') for line in plist: u, p = line.split(':', 1) - 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) + if prog.match(p) is not None and ":" not in p: + hashed_plist_in.append("%s:%s" % (u, p)) + hashed_users.append(u) + else: + 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' - try: - log.debug("Changing password for %s:", users) - util.subp(['chpasswd'], ch_in) - except Exception as e: - errors.append(e) - util.logexc(log, "Failed to set passwords with chpasswd for %s", - users) + if users: + try: + log.debug("Changing password for %s:", users) + util.subp(['chpasswd'], ch_in) + except Exception as e: + errors.append(e) + util.logexc( + log, "Failed to set passwords with chpasswd for %s", users) + + hashed_ch_in = '\n'.join(hashed_plist_in) + '\n' + if hashed_users: + try: + log.debug("Setting hashed password for %s:", hashed_users) + util.subp(['chpasswd', '-e'], hashed_ch_in) + except Exception as e: + errors.append(e) + util.logexc( + log, "Failed to set hashed passwords with chpasswd for %s", + hashed_users) if len(randlist): blurb = ("Set the following 'random' passwords\n", diff --git a/doc/examples/cloud-config.txt b/doc/examples/cloud-config.txt index c03f1026..bd84c641 100644 --- a/doc/examples/cloud-config.txt +++ b/doc/examples/cloud-config.txt @@ -426,14 +426,21 @@ syslog_fix_perms: syslog:root # # there is also an option to set multiple users passwords, using 'chpasswd' # That looks like the following, with 'expire' set to 'True' by default. -# to not expire users passwords, set 'expire' to 'False': +# to not expire users passwords, set 'expire' to 'False'. Also possible +# to set hashed password, here account 'user3' has a password it set to +# 'cloud-init', hashed with SHA-256: # chpasswd: # list: | # user1:password1 # user2:RANDOM +# user3:$5$eriogqzq$Dg7PxHsKGzziuEGkZgkLvacjuEFeljJ.rLf.hZqKQLA # expire: True # ssh_pwauth: [ True, False, "" or "unchanged" ] # +# Hashed passwords can be generated in multiple ways, example with python3: +# python3 -c 'import crypt,getpass; print(crypt.crypt(getpass.getpass(), crypt.mksalt(crypt.METHOD_SHA512)))' +# Newer versions of 'mkpasswd' will also work: mkpasswd -m sha-512 password +# # So, a simple working example to allow login via ssh, and not expire # for the default user would look like: password: passw0rd diff --git a/tests/cloud_tests/configs/modules/set_password_list.yaml b/tests/cloud_tests/configs/modules/set_password_list.yaml index a1eadd75..a2a89c9d 100644 --- a/tests/cloud_tests/configs/modules/set_password_list.yaml +++ b/tests/cloud_tests/configs/modules/set_password_list.yaml @@ -21,11 +21,14 @@ cloud_config: | # sha256 gojanego passwd: "$5$iW$XsxmWCdpwIW8Yhv.Jn/R3uk6A4UaicfW5Xp7C9p9pg." lock_passwd: false + - name: "mikey" + lock_passwd: false chpasswd: list: - tom:mypassword123! - dick:RANDOM - harry:RANDOM + - mikey:$5$xZ$B2YGGEx2AOf4PeW48KC6.QyT1W2B4rZ9Qbltudtha89 collect_scripts: shadow: | #!/bin/bash diff --git a/tests/cloud_tests/configs/modules/set_password_list_string.yaml b/tests/cloud_tests/configs/modules/set_password_list_string.yaml index cbb71bee..c2a0f631 100644 --- a/tests/cloud_tests/configs/modules/set_password_list_string.yaml +++ b/tests/cloud_tests/configs/modules/set_password_list_string.yaml @@ -21,11 +21,14 @@ cloud_config: | # sha256 gojanego passwd: "$5$iW$XsxmWCdpwIW8Yhv.Jn/R3uk6A4UaicfW5Xp7C9p9pg." lock_passwd: false + - name: "mikey" + lock_passwd: false chpasswd: list: | tom:mypassword123! dick:RANDOM harry:RANDOM + mikey:$5$xZ$B2YGGEx2AOf4PeW48KC6.QyT1W2B4rZ9Qbltudtha89 collect_scripts: shadow: | #!/bin/bash diff --git a/tests/cloud_tests/testcases/base.py b/tests/cloud_tests/testcases/base.py index 51ce2b41..64d5507a 100644 --- a/tests/cloud_tests/testcases/base.py +++ b/tests/cloud_tests/testcases/base.py @@ -98,6 +98,9 @@ class PasswordListTest(CloudTestCase): self.assertEqual([], dupes) self.assertEqual(jane_enc, users['jane']) + mikey_enc = "$5$xZ$B2YGGEx2AOf4PeW48KC6.QyT1W2B4rZ9Qbltudtha89" + self.assertEqual(mikey_enc, users['mikey']) + # shadow entry is $N$salt$, so we encrypt with the same format # and salt and expect the result. tom = "mypassword123!" @@ -124,6 +127,7 @@ class PasswordListTest(CloudTestCase): self.assertIn('dick:', out) self.assertIn('harry:', out) self.assertIn('jane:', out) + self.assertIn('mikey:', out) def test_sshd_config(self): """Test sshd config allows passwords""" -- cgit v1.2.3