summaryrefslogtreecommitdiff
path: root/cloudinit
diff options
context:
space:
mode:
authorBen Howard <ben.howard@canonical.com>2012-08-20 14:52:31 -0600
committerBen Howard <ben.howard@canonical.com>2012-08-20 14:52:31 -0600
commit336ddbe13bdfc729495f5bfb8cc89b4360916157 (patch)
tree3d551bde0b99b0db8c1c33f9bbd7e9e22acebb2f /cloudinit
parent4540821caa31dc9ed0bedf521cd36975ddafebfa (diff)
downloadvyos-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.py7
-rw-r--r--cloudinit/config/cc_users_groups.py256
-rw-r--r--cloudinit/distros/__init__.py8
-rw-r--r--cloudinit/distros/ubuntu.py63
-rw-r--r--cloudinit/util.py12
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