diff --git a/bootstrapvz/plugins/admin_user/README.rst b/bootstrapvz/plugins/admin_user/README.rst index 8da68d1..3e5d0f8 100644 --- a/bootstrapvz/plugins/admin_user/README.rst +++ b/bootstrapvz/plugins/admin_user/README.rst @@ -2,11 +2,29 @@ Admin user ---------- This plugin creates a user with passwordless sudo privileges. It also -disables the SSH root login. If the EC2 init scripts are installed, the -script for fetching the SSH authorized keys will be adjust to match the -username specified. +disables the SSH root login. There are three ways to grant access to +the admin user: +- Set a password for the user, or +- Provide a ssh public key to allow remote ssh login, or +- Use the EC2 public key (EC2 machines only) + +If a password is provided, this plugin sets the admin password. This +also re-enables password login (off by default in Jessie). + +If the optional argument pubkey is present (it should be a full path +to a ssh public key), it will ensure that the ssh public key is used +to set up password less remote login for the admin user. + +Only one of these options (password, or pubkey) may be specified. + +If neither the password not a ssh public key location are specified, +and if the EC2 init scripts are installed, the script for fetching the +SSH authorized keys will be adjust to match the username specified. Settings ~~~~~~~~ - ``username``: The username of the account to create. ``required`` +- ``password``: An optional password for the account to create. ``optional`` +- ``pubkey``: The full path to an ssh public key to allow + remote access into the admin account. ``optional`` diff --git a/bootstrapvz/plugins/admin_user/__init__.py b/bootstrapvz/plugins/admin_user/__init__.py index 11888de..b7e8696 100644 --- a/bootstrapvz/plugins/admin_user/__init__.py +++ b/bootstrapvz/plugins/admin_user/__init__.py @@ -4,19 +4,33 @@ def validate_manifest(data, validator, error): import os.path schema_path = os.path.normpath(os.path.join(os.path.dirname(__file__), 'manifest-schema.yml')) validator(data, schema_path) + if ('password' in data['plugins']['admin_user'] and 'pubkey' in data['plugins']['admin_user']): + msg = 'passwd and pubkey are mutually exclusive.' + error(msg, ['plugins', 'admin_user']) + if 'pubkey' in data['plugins']['admin_user']: + full_path = data['plugins']['admin_user']['pubkey'] + if not os.path.exists(full_path): + msg = 'Could not find public key at %s' % full_path + error(msg, ['plugins', 'admin_user']) def resolve_tasks(taskset, manifest): import tasks from bootstrapvz.common.tasks import ssh - from bootstrapvz.providers.ec2.tasks import initd - if initd.AddEC2InitScripts in taskset: - taskset.add(tasks.AdminUserCredentials) from bootstrapvz.common.releases import jessie if manifest.release < jessie: taskset.update([ssh.DisableRootLogin]) + if 'password' in manifest.plugins['admin_user']: + taskset.discard(ssh.DisableSSHPasswordAuthentication) + taskset.add(tasks.AdminUserCredentialsPassword) + else: + if 'pubkey' in manifest.plugins['admin_user']: + taskset.add(tasks.AdminUserCredentialsPublicKey) + else: + taskset.add(tasks.AdminUserCredentialsEc2) + taskset.update([tasks.AddSudoPackage, tasks.CreateAdminUser, tasks.PasswordlessSudo, diff --git a/bootstrapvz/plugins/admin_user/manifest-schema.yml b/bootstrapvz/plugins/admin_user/manifest-schema.yml index 02cce0a..4d20396 100644 --- a/bootstrapvz/plugins/admin_user/manifest-schema.yml +++ b/bootstrapvz/plugins/admin_user/manifest-schema.yml @@ -10,5 +10,11 @@ properties: type: object properties: username: {type: string} + password: {type: string} + pubkey: {$ref: '#/definitions/absolute_path'} required: [username] additionalProperties: false +definitions: + absolute_path: + pattern: ^/[^\0]+$ + type: string diff --git a/bootstrapvz/plugins/admin_user/tasks.py b/bootstrapvz/plugins/admin_user/tasks.py index ae7f221..4293ee9 100644 --- a/bootstrapvz/plugins/admin_user/tasks.py +++ b/bootstrapvz/plugins/admin_user/tasks.py @@ -1,6 +1,9 @@ from bootstrapvz.base import Task from bootstrapvz.common import phases from bootstrapvz.common.tasks.initd import InstallInitScripts +from bootstrapvz.providers.ec2.tasks.initd import AddEC2InitScripts + +import logging import os @@ -41,14 +44,85 @@ class PasswordlessSudo(Task): os.chmod(sudo_admin_path, ug_read_only) -class AdminUserCredentials(Task): - description = 'Modifying ec2-get-credentials to copy the ssh public key to the admin user' +class AdminUserCredentialsPassword(Task): + description = 'Set up access credentials for the admin user with a given password' phase = phases.system_modification - predecessors = [InstallInitScripts] + predecessors = [InstallInitScripts, CreateAdminUser] + + @classmethod + def run(cls, info): + from bootstrapvz.common.tools import log_check_call + log = logging.getLogger(__name__) + + log.debug('Setting the password for the admin user.') + log_check_call( + ['chroot', info.root, 'chpasswd'], + info.manifest.plugins['admin_user']['username'] + + ':' + info.manifest.plugins['admin_user']['password'] + ) + return + + +class AdminUserCredentialsPublicKey(Task): + description = 'Set up access credentials for the admin user with a given public key' + phase = phases.system_modification + predecessors = [AddEC2InitScripts, CreateAdminUser] + successors = [InstallInitScripts] + + @classmethod + def run(cls, info): + from bootstrapvz.common.tools import log_check_call + + log = logging.getLogger(__name__) + + import stat + from shutil import copy + full_path = info.manifest.plugins['admin_user']['pubkey'] + log.debug('Copying public key from {path}'.format(path=full_path)) + + if 'ec2-get-credentials' in info.initd['install']: + log.warn( + 'You are using a static public key for the admin account.' + ' This will conflict with the ec2 public key injection mechanisn.' + ' The ec2-get-credentials startup script has therefore been disabled.') + del info.initd['install']['ec2-get-credentials'] + + username = info.manifest.plugins['admin_user']['username'] + + ssh_file = os.path.join('/home', username, '.ssh/authorized_keys') + rel_ssh_file = os.path.join(info.root, 'home', username, '.ssh/authorized_keys') + + ssh_dir = os.path.dirname(ssh_file) + rel_ssh_dir = os.path.dirname(rel_ssh_file) + if not os.path.exists(rel_ssh_dir): + log.debug('Creating %s.' % rel_ssh_dir) + os.mkdir(rel_ssh_dir) + + log.debug('setting %s mode 700' % rel_ssh_dir) + mode = (stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR) + os.chmod(rel_ssh_dir, mode) + + copy(full_path, rel_ssh_file) + + mode = (stat.S_IRUSR | stat.S_IWUSR) + os.chmod(rel_ssh_file, mode) + + log_check_call(['chroot', info.root, 'chown', '-R', username, ssh_dir]) + return + + +class AdminUserCredentialsEC2(Task): + description = 'Set up access credentials for the admin user using the EC2 credentials' + phase = phases.system_modification + predecessors = [InstallInitScripts, CreateAdminUser] @classmethod def run(cls, info): from bootstrapvz.common.tools import sed_i + log = logging.getLogger(__name__) + getcreds_path = os.path.join(info.root, 'etc/init.d/ec2-get-credentials') + log.debug('Updating EC2 get credentials script.') username = info.manifest.plugins['admin_user']['username'] - sed_i(getcreds_path, 'username=\'root\'', 'username=\'{username}\''.format(username=username)) + sed_i(getcreds_path, "username='root'", + "username='{username}'".format(username=username))