Implemented MP feedback.
This commit is contained in:
parent
7f196c7fd6
commit
126b011fe3
@ -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:
|
||||
|
@ -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)
|
||||
|
@ -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,
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user