diff options
Diffstat (limited to 'cloudinit')
-rw-r--r-- | cloudinit/config/cc_set_passwords.py | 12 | ||||
-rw-r--r-- | cloudinit/config/cc_ssh_import_id.py | 11 | ||||
-rw-r--r-- | cloudinit/config/cc_users_groups.py | 80 | ||||
-rw-r--r-- | cloudinit/distros/__init__.py | 186 | ||||
-rw-r--r-- | cloudinit/distros/ubuntu.py | 55 | ||||
-rw-r--r-- | cloudinit/util.py | 12 |
6 files changed, 347 insertions, 9 deletions
diff --git a/cloudinit/config/cc_set_passwords.py b/cloudinit/config/cc_set_passwords.py index ab266741..4bf62aa9 100644 --- a/cloudinit/config/cc_set_passwords.py +++ b/cloudinit/config/cc_set_passwords.py @@ -50,8 +50,16 @@ def handle(_name, cfg, cloud, log, args): expire = util.get_cfg_option_bool(chfg, 'expire', expire) if not plist and password: - user = util.get_cfg_option_str(cfg, "user", "ubuntu") - plist = "%s:%s" % (user, password) + user = cloud.distro.get_default_user() + + if 'users' in cfg: + user_zero = cfg['users'].keys()[0] + + if user_zero != "default": + user = user_zero + + if user: + plist = "%s:%s" % (user, password) errors = [] if plist: diff --git a/cloudinit/config/cc_ssh_import_id.py b/cloudinit/config/cc_ssh_import_id.py index c58b28ec..9aee2166 100644 --- a/cloudinit/config/cc_ssh_import_id.py +++ b/cloudinit/config/cc_ssh_import_id.py @@ -25,14 +25,21 @@ from cloudinit import util distros = ['ubuntu'] -def handle(name, cfg, _cloud, log, args): +def handle(name, cfg, cloud, log, args): if len(args) != 0: user = args[0] ids = [] if len(args) > 1: ids = args[1:] else: - user = util.get_cfg_option_str(cfg, "user", "ubuntu") + user = cloud.distro.get_default_user() + + if 'users' in cfg: + user_zero = cfg['users'].keys()[0] + + if user_zero != "default": + user = user_zero + ids = util.get_cfg_option_list(cfg, "ssh_import_id", []) if len(ids) == 0: diff --git a/cloudinit/config/cc_users_groups.py b/cloudinit/config/cc_users_groups.py new file mode 100644 index 00000000..7e5ecc7b --- /dev/null +++ b/cloudinit/config/cc_users_groups.py @@ -0,0 +1,80 @@ +# vi: ts=4 expandtab +# +# Copyright (C) 2012 Canonical Ltd. +# +# Author: Ben Howard <ben.howard@canonical.com> +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 3, 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 grp +import pwd +import os +import traceback + +from cloudinit import templater +from cloudinit import util +from cloudinit import ssh_util +from cloudinit.settings import PER_INSTANCE + +frequency = PER_INSTANCE + + +def handle(name, cfg, cloud, log, _args): + groups_cfg = None + users_cfg = None + user_zero = None + + if 'groups' in cfg: + for group in cfg['groups']: + if isinstance(group, dict): + for name, values in group.iteritems(): + if isinstance(values, list): + cloud.distro.create_group(name, values) + elif isinstance(values, str): + cloud.distro.create_group(name, values.split(',')) + else: + cloud.distro.create_group(item, []) + + if 'users' in cfg: + user_zero = None + + for name, user_config in cfg['users'].iteritems(): + if not user_zero: + user_zero = name + + # Handle the default user creation + if name == "default" and user_config: + log.info("Creating default user") + + # Create the default user if so defined + try: + cloud.distro.add_default_user() + + if user_zero == name: + user_zero = cloud.distro.get_default_user() + + except NotImplementedError as e: + + if user_zero == name: + user_zero = None + + log.warn("Distro has not implemented default user " \ + "creation. No default user will be created") + else: + # Make options friendly for distro.create_user + new_opts = {} + if isinstance(user_config, dict): + for opt in user_config: + new_opts[opt.replace('-', '')] = user_config[opt] + + cloud.distro.create_user(name, **new_opts) diff --git a/cloudinit/distros/__init__.py b/cloudinit/distros/__init__.py index 357209a4..2dfb1409 100644 --- a/cloudinit/distros/__init__.py +++ b/cloudinit/distros/__init__.py @@ -7,6 +7,7 @@ # Author: Scott Moser <scott.moser@canonical.com> # Author: Juerg Haefliger <juerg.haefliger@hp.com> # Author: Joshua Harlow <harlowja@yahoo-inc.com> +# Author: Ben Howard <ben.howard@canonical.com> # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License version 3, as @@ -23,12 +24,15 @@ from StringIO import StringIO import abc +import grp import os +import pwd import re from cloudinit import importer from cloudinit import log as logging from cloudinit import util +from cloudinit import ssh_util # TODO(harlowja): Make this via config?? IFACE_ACTIONS = { @@ -47,6 +51,11 @@ class Distro(object): self._paths = paths self._cfg = cfg self.name = name + self.default_user = None + + @abc.abstractmethod + def add_default_user(self): + raise NotImplementedError() @abc.abstractmethod def install_packages(self, pkglist): @@ -170,6 +179,183 @@ class Distro(object): util.logexc(LOG, "Running interface command %s failed", cmd) return False + def isuser(self, name): + try: + if pwd.getpwnam(name): + return True + except KeyError: + return False + + def set_configured_user(self, name): + self.default_user = name + + def get_default_user(self): + return None + + + def create_user(self, name, **kwargs): + """ + Creates users for the system using the GNU passwd tools. This + will work on an GNU system. This should be overriden on + distros where useradd is not desirable or not available. + """ + + if self.isuser(name): + LOG.warn("User %s already exists, skipping." % name) + else: + LOG.debug("Creating name %s" % name) + + adduser_cmd = ['useradd', name] + x_adduser_cmd = adduser_cmd + + # Since we are creating users, we want to carefully validate the + # inputs. If something goes wrong, we can end up with a system + # that nobody can login to. + adduser_opts = { + "gecos": '--comment', + "homedir": '--home', + "primarygroup": '--gid', + "groups": '--groups', + "passwd": '--password', + "shell": '--shell', + "expiredate": '--expiredate', + "inactive": '--inactive', + } + + adduser_opts_flags = { + "nousergroup": '--no-user-group', + "system": '--system', + "nologinit": '--no-log-init', + "nocreatehome": "-M", + } + + # Now check the value and create the command + for option in kwargs: + value = kwargs[option] + if option in adduser_opts and value \ + and isinstance(value, str): + adduser_cmd.extend([adduser_opts[option], value]) + + # Redact the password field from the logs + if option != "password": + x_adduser_cmd.extend([adduser_opts[option], value]) + else: + x_adduser_cmd.extend([adduser_opts[option], 'REDACTED']) + + if option in adduser_opts_flags and value: + adduser_cmd.append(adduser_opts_flags[option]) + x_adduser_cmd.append(adduser_opts_flags[option]) + + # Default to creating home directory unless otherwise directed + # Also, we do not create home directories for system users. + if "nocreatehome" not in kwargs and "system" not in kwargs: + adduser_cmd.append('-m') + + # Create the user + try: + util.subp(adduser_cmd, logstring=x_adduser_cmd) + except Exception as e: + util.logexc(LOG, "Failed to create user %s due to error.", e) + raise e + + # Set password if plain-text password provided + if 'plain_text_passwd' in kwargs and kwargs['plain_text_passwd']: + self.set_passwd(name, kwargs['plain_text_passwd']) + + # Default locking down the account. + if ('lockpasswd' not in kwargs and + ('lockpasswd' in kwargs and kwargs['lockpasswd']) or + 'system' not in kwargs): + try: + util.subp(['passwd', '--lock', name]) + except Exception as e: + util.logexc(LOG, ("Failed to disable password logins for" + "user %s" % name), e) + raise e + + # Configure sudo access + if 'sudo' in kwargs: + self.write_sudo_rules(name, kwargs['sudo']) + + # Import SSH keys + if 'sshauthorizedkeys' in kwargs: + keys = set(kwargs['sshauthorizedkeys']) or [] + ssh_util.setup_user_keys(keys, name, None, self._paths) + + return True + + def set_passwd(self, user, passwd, hashed=False): + pass_string = '%s:%s' % (user, passwd) + cmd = ['chpasswd'] + + if hashed: + cmd.append('--encrypted') + + try: + util.subp(cmd, pass_string, logstring="chpasswd for %s" % user) + except Exception as e: + util.logexc(LOG, "Failed to set password for %s" % user) + raise e + + return True + + def write_sudo_rules(self, + user, + rules, + sudo_file="/etc/sudoers.d/90-cloud-init-users", + ): + + content_header = "# user rules for %s" % user + content = "%s\n%s %s\n\n" % (content_header, user, rules) + + if isinstance(rules, list): + content = "%s\n" % content_header + for rule in rules: + content += "%s %s\n" % (user, rule) + content += "\n" + + if not os.path.exists(sudo_file): + util.write_file(sudo_file, content, 0644) + + else: + try: + with open(sudo_file, 'a') as f: + f.write(content) + except IOError as e: + util.logexc(LOG, "Failed to write %s" % sudo_file, e) + raise e + + def isgroup(self, name): + try: + if grp.getgrpnam(name): + return True + except: + return False + + def create_group(self, name, members): + group_add_cmd = ['groupadd', name] + + # Check if group exists, and then add it doesn't + if self.isgroup(name): + LOG.warn("Skipping creation of existing group '%s'" % name) + else: + try: + util.subp(group_add_cmd) + LOG.info("Created new group %s" % name) + except Exception as e: + util.logexc("Failed to create group %s" % name, e) + + # Add members to the group, if so defined + if len(members) > 0: + for member in members: + if not self.isuser(member): + LOG.warn("Unable to add group member '%s' to group '%s'" + "; user does not exist." % (member, name)) + continue + + util.subp(['usermod', '-a', '-G', name, member]) + LOG.info("Added user '%s' to group '%s'" % (member, name)) + def _get_package_mirror_info(mirror_info, availability_zone=None, mirror_filter=util.search_for_mirror): diff --git a/cloudinit/distros/ubuntu.py b/cloudinit/distros/ubuntu.py index 77c2aff4..423fee73 100644 --- a/cloudinit/distros/ubuntu.py +++ b/cloudinit/distros/ubuntu.py @@ -7,6 +7,7 @@ # Author: Scott Moser <scott.moser@canonical.com> # Author: Juerg Haefliger <juerg.haefliger@hp.com> # Author: Joshua Harlow <harlowja@yahoo-inc.com> +# Author: Ben Howard <ben.howard@canonical.com> # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License version 3, as @@ -20,12 +21,62 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see <http://www.gnu.org/licenses/>. +from cloudinit import distros from cloudinit.distros import debian - +from cloudinit import helpers from cloudinit import log as logging +from cloudinit import util +from cloudinit.settings import PER_INSTANCE +import hashlib +import pwd LOG = logging.getLogger(__name__) class Distro(debian.Distro): - pass + + distro_name = 'ubuntu' + __default_user_name__ = 'ubuntu' + + def __init__(self, name, cfg, paths): + distros.Distro.__init__(self, name, cfg, paths) + # This will be used to restrict certain + # calls from repeatly happening (when they + # should only happen say once per instance...) + self._runner = helpers.Runners(paths) + + def get_default_user(self): + return self.__default_user_name__ + + def add_default_user(self): + # Adds the ubuntu user using the rules: + # - Password is 'ubuntu', but is locked + # - nopasswd sudo access + + self.create_user(self.__default_user_name__, + plain_text_passwd=self.__default_user_name__, + home="/home/%s" % self.__default_user_name__, + shell="/bin/bash", + lockpasswd=True, + gecos="Ubuntu", + sudo="ALL=(ALL) NOPASSWD:ALL") + + LOG.info("Added default 'ubuntu' user with passwordless sudo") + + def create_user(self, name, **kargs): + + if not super(Distro, self).create_user(name, **kargs): + return False + + if 'sshimportid' in kargs: + cmd = ["sudo", "-Hu", name, "ssh-import-id"] + kargs['sshimportid'] + LOG.debug("Importing ssh ids for user %s, post user creation." + % name) + + try: + util.subp(cmd, capture=True) + except util.ProcessExecutionError as e: + util.logexc(LOG, "Failed to import %s ssh ids", name) + raise e + + return True diff --git a/cloudinit/util.py b/cloudinit/util.py index 825867a7..7d56e8be 100644 --- a/cloudinit/util.py +++ b/cloudinit/util.py @@ -1330,12 +1330,18 @@ def delete_dir_contents(dirname): del_file(node_fullpath) -def subp(args, data=None, rcs=None, env=None, capture=True, shell=False): +def subp(args, data=None, rcs=None, env=None, capture=True, shell=False, logstring=False): if rcs is None: rcs = [0] try: - LOG.debug(("Running command %s with allowed return codes %s" - " (shell=%s, capture=%s)"), args, rcs, shell, capture) + + if not logstring: + LOG.debug(("Running command %s with allowed return codes %s" + " (shell=%s, capture=%s)"), args, rcs, shell, capture) + else: + LOG.debug(("Running hidden command to protect sensitive input/output " + " logstring: %s" ), logstring) + if not capture: stdout = None stderr = None |