Implemented MP feedback.

This commit is contained in:
Ben Howard 2012-08-21 13:03:47 -06:00
parent 7f196c7fd6
commit 126b011fe3
4 changed files with 246 additions and 250 deletions

View File

@ -25,19 +25,20 @@ 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 = None
try:
user = cloud.distro.get_default_username()
user = cloud.distro.get_configured_user()
except NotImplementedError:
pass
user = None
ids = util.get_cfg_option_list(cfg, "ssh_import_id", [])
if len(ids) == 0:

View File

@ -28,236 +28,57 @@ from cloudinit.settings import PER_INSTANCE
frequency = PER_INSTANCE
def handle(name, cfg, cloud, log, _args):
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)
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:
users_cfg = cfg['users']
user_zero = users_cfg.keys()[0]
user_zero = None
for name, user_config in users_cfg.iteritems():
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()
cloud.distro.add_default_user()
if user_zero == name:
user_zero = cloud.distro.get_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
if user_zero == name:
user_zero = None
log.warn("Distro has not implemented default user " \
"creation. No default user will be created")
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]
x_adduser_cmd = adduser_cmd
adduser_opts = {
"gecos": '--comment',
"homedir": '--home',
"primary-group": '--gid',
"groups": '--groups',
"passwd": '--password',
"shell": '--shell',
"expiredate": '--expiredate',
"inactive": '--inactive',
}
adduser_opts_flags = {
"no-user-group": '--no-user-group',
"system": '--system',
"no-log-init": '--no-log-init',
"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])
# 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 "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, logstring=x_adduser_cmd)
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))
# 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)
if user_zero:
cloud.distro.set_configured_user(user_zero)
log.info("Set configured user for this instance to %s" % user_zero)

View File

@ -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,10 +24,13 @@
from StringIO import StringIO
import abc
import pwd
import grp
import os
from cloudinit import importer
from cloudinit import log as logging
from cloudinit import util
from cloudinit import ssh_util
# TODO: Make this via config??
IFACE_ACTIONS = {
@ -51,7 +55,7 @@ class Distro(object):
raise NotImplementedError()
@abc.abstractmethod
def get_default_username(self):
def get_default_user(self):
raise NotImplementedError()
@abc.abstractmethod
@ -158,6 +162,185 @@ 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_configured_user(self):
try:
return getattr(self, 'default_user')
except:
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)
return False
# 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)
return False
# 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)
return False
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)
f.close()
except IOError as e:
util.logexc(LOG, "Failed to write %s" % sudo_file, 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 fetch(name):
locs = importer.find_module(name,

View File

@ -27,7 +27,7 @@ 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__)
@ -36,7 +36,7 @@ LOG = logging.getLogger(__name__)
class Distro(debian.Distro):
distro_name = 'ubuntu'
__default_user_name__ = 'ubuntu-test'
__default_user_name__ = 'ubuntu'
def __init__(self, name, cfg, paths):
distros.Distro.__init__(self, name, cfg, paths)
@ -45,7 +45,7 @@ class Distro(debian.Distro):
# should only happen say once per instance...)
self._runner = helpers.Runners(paths)
def get_default_username(self):
def get_default_user(self):
return self.__default_user_name__
def add_default_user(self):
@ -53,39 +53,30 @@ class Distro(debian.Distro):
# - 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")
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
LOG.info("Added default 'ubuntu' user with passwordless sudo")
try:
util.subp(['adduser',
'--shell', '/bin/bash',
'--home', '/home/%s' % self.__default_user_name__,
'--disabled-password',
'--gecos', 'Ubuntu',
self.__default_user_name__,
])
def create_user(self, name, **kargs):
pass_string = '%(u)s:%(u)s' % {'u': self.__default_user_name__}
x_pass_string = '%(u)s:REDACTED' % {'u': self.__default_user_name__}
util.subp(['chpasswd'], pass_string, logstring=x_pass_string)
util.subp(['passwd', '-l', self.__default_user_name__])
if not super(Distro, self).create_user(name, **kargs):
return False
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__ }
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)
util.write_file('/etc/sudoers.d/90-cloud-init-ubuntu',
ubuntu_sudoers,
mode=0440)
try:
util.subp(cmd, capture=True)
except util.ProcessExecutionError as e:
util.logexc(LOG, "Failed to import %s ssh ids", name)
raise e
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))
return True