Blame cloudinit/config/cc_set_passwords.py

Packit Service a04d08
# Copyright (C) 2009-2010 Canonical Ltd.
Packit Service a04d08
# Copyright (C) 2012, 2013 Hewlett-Packard Development Company, L.P.
Packit Service a04d08
#
Packit Service a04d08
# Author: Scott Moser <scott.moser@canonical.com>
Packit Service a04d08
# Author: Juerg Haefliger <juerg.haefliger@hp.com>
Packit Service a04d08
#
Packit Service a04d08
# This file is part of cloud-init. See LICENSE file for license information.
Packit Service a04d08
Packit Service a04d08
"""
Packit Service a04d08
Set Passwords
Packit Service a04d08
-------------
Packit Service a04d08
**Summary:** Set user passwords and enable/disable SSH password authentication
Packit Service a04d08
Packit Service a04d08
This module consumes three top-level config keys: ``ssh_pwauth``, ``chpasswd``
Packit Service a04d08
and ``password``.
Packit Service a04d08
Packit Service a04d08
The ``ssh_pwauth`` config key determines whether or not sshd will be configured
Packit Service a04d08
to accept password authentication.  True values will enable password auth,
Packit Service a04d08
false values will disable password auth, and the literal string ``unchanged``
Packit Service a04d08
will leave it unchanged.  Setting no value will also leave the current setting
Packit Service a04d08
on-disk unchanged.
Packit Service a04d08
Packit Service a04d08
The ``chpasswd`` config key accepts a dictionary containing either or both of
Packit Service a04d08
``expire`` and ``list``.
Packit Service a04d08
Packit Service a04d08
If the ``list`` key is provided, it should contain a list of
Packit Service a04d08
``username:password`` pairs.  This can be either a YAML list (of strings), or a
Packit Service a04d08
multi-line string with one pair per line.  Each user will have the
Packit Service a04d08
corresponding password set.  A password can be randomly generated by specifying
Packit Service a04d08
``RANDOM`` or ``R`` as a user's password.  A hashed password, created by a tool
Packit Service a04d08
like ``mkpasswd``, can be specified; a regex
Packit Service a04d08
(``r'\\$(1|2a|2y|5|6)(\\$.+){2}'``) is used to determine if a password value
Packit Service a04d08
should be treated as a hash.
Packit Service a04d08
Packit Service a04d08
.. note::
Packit Service a04d08
    The users specified must already exist on the system.  Users will have been
Packit Service a04d08
    created by the ``cc_users_groups`` module at this point.
Packit Service a04d08
Packit Service a04d08
By default, all users on the system will have their passwords expired (meaning
Packit Service a04d08
that they will have to be reset the next time the user logs in).  To disable
Packit Service a04d08
this behaviour, set ``expire`` under ``chpasswd`` to a false value.
Packit Service a04d08
Packit Service a04d08
If a ``list`` of user/password pairs is not specified under ``chpasswd``, then
Packit Service a04d08
the value of the ``password`` config key will be used to set the default user's
Packit Service a04d08
password.
Packit Service a04d08
Packit Service a04d08
**Internal name:** ``cc_set_passwords``
Packit Service a04d08
Packit Service a04d08
**Module frequency:** per instance
Packit Service a04d08
Packit Service a04d08
**Supported distros:** all
Packit Service a04d08
Packit Service a04d08
**Config keys**::
Packit Service a04d08
Packit Service a04d08
    ssh_pwauth: <yes/no/unchanged>
Packit Service a04d08
Packit Service a04d08
    password: password1
Packit Service a04d08
    chpasswd:
Packit Service a04d08
        expire: <true/false>
Packit Service a04d08
Packit Service a04d08
    chpasswd:
Packit Service a04d08
        list: |
Packit Service a04d08
            user1:password1
Packit Service a04d08
            user2:RANDOM
Packit Service a04d08
            user3:password3
Packit Service a04d08
            user4:R
Packit Service a04d08
Packit Service a04d08
    ##
Packit Service a04d08
    # or as yaml list
Packit Service a04d08
    ##
Packit Service a04d08
    chpasswd:
Packit Service a04d08
        list:
Packit Service a04d08
            - user1:password1
Packit Service a04d08
            - user2:RANDOM
Packit Service a04d08
            - user3:password3
Packit Service a04d08
            - user4:R
Packit Service a04d08
            - user4:$6$rL..$ej...
Packit Service a04d08
"""
Packit Service a04d08
Packit Service a04d08
import re
Packit Service a04d08
import sys
Packit Service a04d08
Packit Service a04d08
from cloudinit.distros import ug_util
Packit Service a04d08
from cloudinit import log as logging
Packit Service a04d08
from cloudinit.ssh_util import update_ssh_config
Packit Service 751c4a
from cloudinit import subp
Packit Service a04d08
from cloudinit import util
Packit Service a04d08
Packit Service a04d08
from string import ascii_letters, digits
Packit Service a04d08
Packit Service a04d08
LOG = logging.getLogger(__name__)
Packit Service a04d08
Packit Service a04d08
# We are removing certain 'painful' letters/numbers
Packit Service a04d08
PW_SET = (''.join([x for x in ascii_letters + digits
Packit Service a04d08
                   if x not in 'loLOI01']))
Packit Service a04d08
Packit Service a04d08
Packit Service a04d08
def handle_ssh_pwauth(pw_auth, service_cmd=None, service_name="ssh"):
Packit Service a04d08
    """Apply sshd PasswordAuthentication changes.
Packit Service a04d08
Packit Service a04d08
    @param pw_auth: config setting from 'pw_auth'.
Packit Service a04d08
                    Best given as True, False, or "unchanged".
Packit Service a04d08
    @param service_cmd: The service command list (['service'])
Packit Service a04d08
    @param service_name: The name of the sshd service for the system.
Packit Service a04d08
Packit Service a04d08
    @return: None"""
Packit Service a04d08
    cfg_name = "PasswordAuthentication"
Packit Service a04d08
    if service_cmd is None:
Packit Service a04d08
        service_cmd = ["service"]
Packit Service a04d08
Packit Service a04d08
    if util.is_true(pw_auth):
Packit Service a04d08
        cfg_val = 'yes'
Packit Service a04d08
    elif util.is_false(pw_auth):
Packit Service a04d08
        cfg_val = 'no'
Packit Service a04d08
    else:
Packit Service 751c4a
        bmsg = "Leaving SSH config '%s' unchanged." % cfg_name
Packit Service a04d08
        if pw_auth is None or pw_auth.lower() == 'unchanged':
Packit Service a04d08
            LOG.debug("%s ssh_pwauth=%s", bmsg, pw_auth)
Packit Service a04d08
        else:
Packit Service a04d08
            LOG.warning("%s Unrecognized value: ssh_pwauth=%s", bmsg, pw_auth)
Packit Service a04d08
        return
Packit Service a04d08
Packit Service a04d08
    updated = update_ssh_config({cfg_name: cfg_val})
Packit Service a04d08
    if not updated:
Packit Service 751c4a
        LOG.debug("No need to restart SSH service, %s not updated.", cfg_name)
Packit Service a04d08
        return
Packit Service a04d08
Packit Service a04d08
    if 'systemctl' in service_cmd:
Packit Service a04d08
        cmd = list(service_cmd) + ["restart", service_name]
Packit Service a04d08
    else:
Packit Service a04d08
        cmd = list(service_cmd) + [service_name, "restart"]
Packit Service 751c4a
    subp.subp(cmd)
Packit Service 751c4a
    LOG.debug("Restarted the SSH daemon.")
Packit Service a04d08
Packit Service a04d08
Packit Service a04d08
def handle(_name, cfg, cloud, log, args):
Packit Service a04d08
    if len(args) != 0:
Packit Service a04d08
        # if run from command line, and give args, wipe the chpasswd['list']
Packit Service a04d08
        password = args[0]
Packit Service a04d08
        if 'chpasswd' in cfg and 'list' in cfg['chpasswd']:
Packit Service a04d08
            del cfg['chpasswd']['list']
Packit Service a04d08
    else:
Packit Service a04d08
        password = util.get_cfg_option_str(cfg, "password", None)
Packit Service a04d08
Packit Service a04d08
    expire = True
Packit Service a04d08
    plist = None
Packit Service a04d08
Packit Service a04d08
    if 'chpasswd' in cfg:
Packit Service a04d08
        chfg = cfg['chpasswd']
Packit Service a04d08
        if 'list' in chfg and chfg['list']:
Packit Service a04d08
            if isinstance(chfg['list'], list):
Packit Service a04d08
                log.debug("Handling input for chpasswd as list.")
Packit Service a04d08
                plist = util.get_cfg_option_list(chfg, 'list', plist)
Packit Service a04d08
            else:
Packit Service a04d08
                log.debug("Handling input for chpasswd as multiline string.")
Packit Service a04d08
                plist = util.get_cfg_option_str(chfg, 'list', plist)
Packit Service a04d08
                if plist:
Packit Service a04d08
                    plist = plist.splitlines()
Packit Service a04d08
Packit Service a04d08
        expire = util.get_cfg_option_bool(chfg, 'expire', expire)
Packit Service a04d08
Packit Service a04d08
    if not plist and password:
Packit Service a04d08
        (users, _groups) = ug_util.normalize_users_groups(cfg, cloud.distro)
Packit Service a04d08
        (user, _user_config) = ug_util.extract_default(users)
Packit Service a04d08
        if user:
Packit Service a04d08
            plist = ["%s:%s" % (user, password)]
Packit Service a04d08
        else:
Packit Service a04d08
            log.warning("No default or defined user to change password for.")
Packit Service a04d08
Packit Service a04d08
    errors = []
Packit Service a04d08
    if plist:
Packit Service a04d08
        plist_in = []
Packit Service a04d08
        hashed_plist_in = []
Packit Service a04d08
        hashed_users = []
Packit Service a04d08
        randlist = []
Packit Service a04d08
        users = []
Packit Service a04d08
        # N.B. This regex is included in the documentation (i.e. the module
Packit Service a04d08
        # docstring), so any changes to it should be reflected there.
Packit Service a04d08
        prog = re.compile(r'\$(1|2a|2y|5|6)(\$.+){2}')
Packit Service a04d08
        for line in plist:
Packit Service a04d08
            u, p = line.split(':', 1)
Packit Service a04d08
            if prog.match(p) is not None and ":" not in p:
Packit Service a04d08
                hashed_plist_in.append(line)
Packit Service a04d08
                hashed_users.append(u)
Packit Service a04d08
            else:
Packit Service a04d08
                # in this else branch, we potentially change the password
Packit Service a04d08
                # hence, a deviation from .append(line)
Packit Service a04d08
                if p == "R" or p == "RANDOM":
Packit Service a04d08
                    p = rand_user_password()
Packit Service a04d08
                    randlist.append("%s:%s" % (u, p))
Packit Service a04d08
                plist_in.append("%s:%s" % (u, p))
Packit Service a04d08
                users.append(u)
Packit Service a04d08
        ch_in = '\n'.join(plist_in) + '\n'
Packit Service a04d08
        if users:
Packit Service a04d08
            try:
Packit Service a04d08
                log.debug("Changing password for %s:", users)
Packit Service a04d08
                chpasswd(cloud.distro, ch_in)
Packit Service a04d08
            except Exception as e:
Packit Service a04d08
                errors.append(e)
Packit Service a04d08
                util.logexc(
Packit Service a04d08
                    log, "Failed to set passwords with chpasswd for %s", users)
Packit Service a04d08
Packit Service a04d08
        hashed_ch_in = '\n'.join(hashed_plist_in) + '\n'
Packit Service a04d08
        if hashed_users:
Packit Service a04d08
            try:
Packit Service a04d08
                log.debug("Setting hashed password for %s:", hashed_users)
Packit Service a04d08
                chpasswd(cloud.distro, hashed_ch_in, hashed=True)
Packit Service a04d08
            except Exception as e:
Packit Service a04d08
                errors.append(e)
Packit Service a04d08
                util.logexc(
Packit Service a04d08
                    log, "Failed to set hashed passwords with chpasswd for %s",
Packit Service a04d08
                    hashed_users)
Packit Service a04d08
Packit Service a04d08
        if len(randlist):
Packit Service a04d08
            blurb = ("Set the following 'random' passwords\n",
Packit Service a04d08
                     '\n'.join(randlist))
Packit Service a04d08
            sys.stderr.write("%s\n%s\n" % blurb)
Packit Service a04d08
Packit Service a04d08
        if expire:
Packit Service a04d08
            expired_users = []
Packit Service a04d08
            for u in users:
Packit Service a04d08
                try:
Packit Service a04d08
                    cloud.distro.expire_passwd(u)
Packit Service a04d08
                    expired_users.append(u)
Packit Service a04d08
                except Exception as e:
Packit Service a04d08
                    errors.append(e)
Packit Service a04d08
                    util.logexc(log, "Failed to set 'expire' for %s", u)
Packit Service a04d08
            if expired_users:
Packit Service a04d08
                log.debug("Expired passwords for: %s users", expired_users)
Packit Service a04d08
Packit Service a04d08
    handle_ssh_pwauth(
Packit Service a04d08
        cfg.get('ssh_pwauth'), service_cmd=cloud.distro.init_cmd,
Packit Service a04d08
        service_name=cloud.distro.get_option('ssh_svcname', 'ssh'))
Packit Service a04d08
Packit Service a04d08
    if len(errors):
Packit Service a04d08
        log.debug("%s errors occured, re-raising the last one", len(errors))
Packit Service a04d08
        raise errors[-1]
Packit Service a04d08
Packit Service a04d08
Packit Service 751c4a
def rand_user_password(pwlen=20):
Packit Service a04d08
    return util.rand_str(pwlen, select_from=PW_SET)
Packit Service a04d08
Packit Service a04d08
Packit Service a04d08
def chpasswd(distro, plist_in, hashed=False):
Packit Service 751c4a
    if util.is_BSD():
Packit Service a04d08
        for pentry in plist_in.splitlines():
Packit Service a04d08
            u, p = pentry.split(":")
Packit Service a04d08
            distro.set_passwd(u, p, hashed=hashed)
Packit Service a04d08
    else:
Packit Service a04d08
        cmd = ['chpasswd'] + (['-e'] if hashed else [])
Packit Service 751c4a
        subp.subp(cmd, plist_in)
Packit Service a04d08
Packit Service a04d08
# vi: ts=4 expandtab