Skip to content

Commit

Permalink
Add support for setting hashed passwords
Browse files Browse the repository at this point in the history
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
  • Loading branch information
toredash authored and smoser committed Mar 24, 2017
1 parent 4a2b2f8 commit 2163297
Show file tree
Hide file tree
Showing 5 changed files with 53 additions and 14 deletions.
48 changes: 35 additions & 13 deletions cloudinit/config/cc_set_passwords.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@
``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::
Expand Down Expand Up @@ -60,8 +61,10 @@
- user2:RANDOM
- user3:password3
- user4:R
- user4:$6$rL..$ej...
"""

import re
import sys

from cloudinit.distros import ug_util
Expand Down Expand Up @@ -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",
Expand Down
9 changes: 8 additions & 1 deletion doc/examples/cloud-config.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
3 changes: 3 additions & 0 deletions tests/cloud_tests/configs/modules/set_password_list.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 4 additions & 0 deletions tests/cloud_tests/testcases/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,9 @@ def test_shadow_passwords(self):
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!"
Expand All @@ -124,6 +127,7 @@ def test_shadow_expected_users(self):
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"""
Expand Down

0 comments on commit 2163297

Please sign in to comment.