diff options
author | Ben Howard <ben.howard@canonical.com> | 2012-08-20 14:52:31 -0600 |
---|---|---|
committer | Ben Howard <ben.howard@canonical.com> | 2012-08-20 14:52:31 -0600 |
commit | 336ddbe13bdfc729495f5bfb8cc89b4360916157 (patch) | |
tree | 3d551bde0b99b0db8c1c33f9bbd7e9e22acebb2f /cloudinit | |
parent | 4540821caa31dc9ed0bedf521cd36975ddafebfa (diff) | |
download | vyos-cloud-init-336ddbe13bdfc729495f5bfb8cc89b4360916157.tar.gz vyos-cloud-init-336ddbe13bdfc729495f5bfb8cc89b4360916157.zip |
Added "userless" mode to cloud-init for handling the creation of the
users and the default user on Ubuntu.
cloudinit/config/cc_users_groups.py: new cloud-config module for creating
users and groups on instance initialization.
- Creates users and group
- Sets "user" directive used in ssh_import_id
cloudinit/config/cc_ssh_import_id.py: module will rely upon users_groups
for setting the default user. Removed assumption of 'ubuntu' user.
cloudinit/distros/__init__.py: Added new abstract methods for getting
and creating the default user.
cloudinit/distros/ubuntu.py: Defined abstract methods for getting and
and creating the default 'ubuntu' user on Ubuntu instances.
cloudinit/util.py: Added ability to hide command run through util.subp to
prevent the commands from showing in the logs. Used by user_groups
cloud-config module.
config/cloud.cfg: Removed "user: ubuntu" directive and replaced with new
user-less syntax.
doc/examples/cloud-config.txt: Documented the creation of users and groups.
Diffstat (limited to 'cloudinit')
-rw-r--r-- | cloudinit/config/cc_ssh_import_id.py | 7 | ||||
-rw-r--r-- | cloudinit/config/cc_users_groups.py | 256 | ||||
-rw-r--r-- | cloudinit/distros/__init__.py | 8 | ||||
-rw-r--r-- | cloudinit/distros/ubuntu.py | 63 | ||||
-rw-r--r-- | cloudinit/util.py | 12 |
5 files changed, 340 insertions, 6 deletions
diff --git a/cloudinit/config/cc_ssh_import_id.py b/cloudinit/config/cc_ssh_import_id.py index c58b28ec..f18e1fc5 100644 --- a/cloudinit/config/cc_ssh_import_id.py +++ b/cloudinit/config/cc_ssh_import_id.py @@ -32,7 +32,12 @@ def handle(name, cfg, _cloud, log, args): if len(args) > 1: ids = args[1:] else: - user = util.get_cfg_option_str(cfg, "user", "ubuntu") + try: + user = cloud.distro.get_default_username() + except NotImplementedError: + pass + + user = None 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..1a428217 --- /dev/null +++ b/cloudinit/config/cc_users_groups.py @@ -0,0 +1,256 @@ +# 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: + groups_cfg = cfg['groups'] + create_groups(groups_cfg, log) + + if 'users' in cfg: + users_cfg = cfg['users'] + user_zero = users_cfg.keys()[0] + + for name, user_config in users_cfg.iteritems(): + if name == "default" and user_config: + log.info("Creating default user") + + # Create the default user if so defined + try: + cloud.distro.add_default_user() + + except NotImplementedError as e: + log.warn(("Distro has not implemented default user" + "creation. No default user will be created")) + + # Get the distro user + if user_zero == 'default': + try: + user_zero = cloud.distro.get_default_username() + + except NotImplementedError: + pass + + else: + create_user(name, user_config, log, cloud) + + # Override user directive + if user_zero and check_user(user_zero): + cfg['user'] = user_zero + log.info("Override user directive with '%s'" % user_zero) + + +def check_user(user): + try: + user = pwd.getpwnam(user) + return True + + except KeyError: + return False + + return False + +def create_user(user, user_config, log, cloud): + # Iterate over the users definition and create the users + + if check_user(user): + log.warn("User %s already exists, skipping." % user) + + else: + log.info("Creating user %s" % user) + + adduser_cmd = ['useradd', user] + adduser_opts = { + "gecos": '-c', + "homedir": '--home', + "primary-group": '-g', + "groups": '-G', + "passwd": '-p', + "shell": '-s', + "expiredate": '-e', + "inactive": '-f', + } + + adduser_opts_flags = { + "no-user-group": '-N', + "system": '-r', + "no-log-init": '-l', + "no-create-home": "-M", + } + + # Now check the value and create the command + for option in user_config: + value = user_config[option] + if option in adduser_opts and value \ + and type(value).__name__ == "str": + adduser_cmd.extend([adduser_opts[option], value]) + + if option in adduser_opts_flags and value: + 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 "no-create-home" not in user_config and \ + "system" not in user_config: + adduser_cmd.append('-m') + + print adduser_cmd + + # Create the user + try: + util.subp(adduser_cmd, + hidden="cloudinit.user_config.cc_users_groups(%s)" % user) + + except Exception as e: + log.warn("Failed to create user %s due to error.\n%s" % user) + + + # Double check to make sure that the user exists + if not check_user(user): + log.warn("User creation for %s failed for unknown reasons" % user) + return False + + # unlock the password if so-user_configured + if 'lock-passwd' not in user_config or \ + user_config['lock-passwd']: + + try: + util.subp(['passwd', '-l', user]) + + except Exception as e: + log.warn("Failed to disable password logins for user %s\n%s" \ + % (user, e)) + + # write out sudo options + if 'sudo' in user_config: + write_sudo(user, user_config['sudo'], log) + + # import ssh id's from launchpad + if 'ssh-import-id' in user_config: + import_ssh_id(user, user_config['ssh-import-id'], log) + + # write ssh-authorized-keys + if 'ssh-authorized-keys' in user_config: + keys = set(user_config['ssh-authorized-keys']) or [] + user_home = pwd.getpwnam(user).pw_dir + ssh_util.setup_user_keys(keys, user, None, cloud.paths) + +def import_ssh_id(user, keys, log): + + if not os.path.exists('/usr/bin/ssh-import-id'): + log.warn("ssh-import-id does not exist on this system, skipping") + return + + cmd = ["sudo", "-Hu", user, "ssh-import-id"] + keys + log.debug("Importing ssh ids for user %s.", user) + + try: + util.subp(cmd, capture=False) + + except util.ProcessExecutionError as e: + log.warn("Failed to run command to import %s ssh ids", user) + log.warn(traceback.print_exc(e)) + + +def write_sudo(user, rules, log): + sudo_file = "/etc/sudoers.d/90-cloud-init-users" + + content = "%s %s" % (user, rules) + if type(rules).__name__ == "list": + content = "" + for rule in rules: + content += "%s %s\n" % (user, rule) + + if not os.path.exists(sudo_file): + content = "# Added by cloud-init\n%s\n" % content + util.write_file(sudo_file, content, 0644) + + else: + old_content = None + try: + with open(sudo_file, 'r') as f: + old_content = f.read() + f.close() + + except IOError as e: + log.warn("Failed to read %s, not adding sudo rules for %s" % \ + (sudo_file, user)) + + content = "%s\n\n%s" % (old_content, content) + util.write_file(sudo_file, content, 0644) + +def create_groups(groups, log): + existing_groups = [x.gr_name for x in grp.getgrall()] + existing_users = [x.pw_name for x in pwd.getpwall()] + + for group in groups: + + group_add_cmd = ['groupadd'] + group_name = None + group_members = [] + + if type(group).__name__ == "dict": + group_name = [ x for x in group ][0] + for user in group[group_name]: + if user in existing_users: + group_members.append(user) + else: + log.warn("Unable to add non-existant user '%s' to" \ + " group '%s'" % (user, group_name)) + else: + group_name = group + group_add_cmd.append(group) + + group_add_cmd.append(group_name) + + # Check if group exists, and then add it doesn't + if group_name in existing_groups: + log.warn("Group '%s' already exists, skipping creation." % \ + group_name) + + else: + try: + util.subp(group_add_cmd) + log.info("Created new group %s" % group) + + except Exception as e: + log.warn("Failed to create group %s\n%s" % (group, e)) + + # Add members to the group, if so defined + if len(group_members) > 0: + for member in group_members: + util.subp(['usermod', '-a', '-G', group_name, member]) + log.info("Added user '%s' to group '%s'" % (member, group)) + + diff --git a/cloudinit/distros/__init__.py b/cloudinit/distros/__init__.py index da4d0180..8aec1199 100644 --- a/cloudinit/distros/__init__.py +++ b/cloudinit/distros/__init__.py @@ -47,6 +47,14 @@ class Distro(object): self.name = name @abc.abstractmethod + def add_default_user(self): + raise NotImplementedError() + + @abc.abstractmethod + def get_default_username(self): + raise NotImplementedError() + + @abc.abstractmethod def install_packages(self, pkglist): raise NotImplementedError() diff --git a/cloudinit/distros/ubuntu.py b/cloudinit/distros/ubuntu.py index 77c2aff4..e6672c4f 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,70 @@ # 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 pwd LOG = logging.getLogger(__name__) class Distro(debian.Distro): - pass + + distro_name = 'ubuntu' + __default_user_name__ = 'ubuntu-test' + + 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_username(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 + + + if self.__default_user_name__ in [x[0] for x in pwd.getpwall()]: + LOG.warn("'%s' user already exists, not creating it." % \ + self.__default_user_name__) + return + + try: + util.subp(['adduser', + '--shell', '/bin/bash', + '--home', '/home/%s' % self.__default_user_name__, + '--disabled-password', + '--gecos', 'Ubuntu', + self.__default_user_name__, + ]) + + pass_string = '%(u)s:%(u)s' % {'u': self.__default_user_name__} + util.subp(['chpasswd'], pass_string) + util.subp(['passwd', '-l', self.__default_user_name__]) + + ubuntu_sudoers=""" +# Added by cloud-init +# %(user)s user is default user in cloud-images. +# It needs passwordless sudo functionality. +%(user)s ALL=(ALL) NOPASSWD:ALL +""" % { 'user': self.__default_user_name__ } + + util.write_file('/etc/sudoers.d/90-cloud-init-ubuntu', + ubuntu_sudoers, + mode=0440) + + LOG.info("Added default 'ubuntu' user with passwordless sudo") + + except Exception as e: + util.logexc(LOG, "Failed to create %s user\n%s" % + (self.__default_user_name__, e)) diff --git a/cloudinit/util.py b/cloudinit/util.py index a8c0cceb..0fbf9832 100644 --- a/cloudinit/util.py +++ b/cloudinit/util.py @@ -1329,12 +1329,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, hidden=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 hidden: + 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 sensative output " + " Calling function: %s" ), hidden) + if not capture: stdout = None stderr = None |