admin_user: Allow pubkey & password to be used together

Also change README a little, add some comments and get the code
a little more in line with the style of bootstrap-vz
This commit is contained in:
Anders Ingemann 2016-02-10 21:56:52 +01:00
parent a6e4b40268
commit ddfd8a2fd3
3 changed files with 66 additions and 76 deletions

View file

@ -4,27 +4,40 @@ Admin user
This plugin creates a user with passwordless sudo privileges. It also This plugin creates a user with passwordless sudo privileges. It also
disables the SSH root login. There are three ways to grant access to disables the SSH root login. There are three ways to grant access to
the admin user: 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) - Use the EC2 public key (EC2 machines only)
- Set a password for the user
- Provide a SSH public key to allow remote SSH login
If a password is provided, this plugin sets the admin password. This If the EC2 init scripts are installed, the script for fetching the
also re-enables password login (off by default in Jessie). SSH authorized keys will be adjusted to match the username
specified in ``username``.
If the optional argument pubkey is present (it should be a full path If a password is provided (the ``password`` setting),
to a ssh public key), it will ensure that the ssh public key is used this plugin sets the admin password, which also re-enables
to set up password less remote login for the admin user. SSH password login (off by default in Jessie or newer).
Only one of these options (password, or pubkey) may be specified. If the optional setting ``pubkey`` is present (it should be a full path
to a SSH public key), you will be able to log in to the admin user account
using the corresponding private key
(this disables the EC2 public key injection mechanism).
If neither the password not a ssh public key location are specified, The ``password`` and ``pubkey`` settings can be used at the same time.
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 Settings
~~~~~~~~ ~~~~~~~~
- ``username``: The username of the account to create. ``required`` - ``username``: The username of the account to create. ``required``
- ``password``: An optional password for the account to create. ``optional`` - ``password``: An optional password for the account to create. ``optional``
- ``pubkey``: The full path to an ssh public key to allow - ``pubkey``: The full path to an SSH public key to allow
remote access into the admin account. ``optional`` remote access into the admin account. ``optional``
Example:
.. code:: yaml
---
plugins:
admin_user:
username: admin
password: s3cr3t
pubkey: /home/bootstrap-vz/.ssh/id_rsa

View file

@ -4,14 +4,10 @@ def validate_manifest(data, validator, error):
import os.path import os.path
schema_path = os.path.normpath(os.path.join(os.path.dirname(__file__), 'manifest-schema.yml')) schema_path = os.path.normpath(os.path.join(os.path.dirname(__file__), 'manifest-schema.yml'))
validator(data, schema_path) validator(data, schema_path)
if ('password' in data['plugins']['admin_user'] and 'pubkey' in data['plugins']['admin_user']): pubkey = data['plugins']['admin_user'].get('pubkey', None)
msg = 'passwd and pubkey are mutually exclusive.' if pubkey is not None and not os.path.exists(pubkey):
error(msg, ['plugins', 'admin_user']) msg = 'Could not find public key at %s' % pubkey
if 'pubkey' in data['plugins']['admin_user']: error(msg, ['plugins', 'admin_user', 'pubkey'])
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): def resolve_tasks(taskset, manifest):
@ -25,7 +21,6 @@ def resolve_tasks(taskset, manifest):
if 'password' in manifest.plugins['admin_user']: if 'password' in manifest.plugins['admin_user']:
taskset.discard(ssh.DisableSSHPasswordAuthentication) taskset.discard(ssh.DisableSSHPasswordAuthentication)
taskset.add(tasks.AdminUserCredentialsPassword) taskset.add(tasks.AdminUserCredentialsPassword)
else:
if 'pubkey' in manifest.plugins['admin_user']: if 'pubkey' in manifest.plugins['admin_user']:
taskset.add(tasks.AdminUserCredentialsPublicKey) taskset.add(tasks.AdminUserCredentialsPublicKey)
else: else:

View file

@ -3,8 +3,9 @@ from bootstrapvz.common import phases
from bootstrapvz.common.tasks.initd import InstallInitScripts from bootstrapvz.common.tasks.initd import InstallInitScripts
from bootstrapvz.providers.ec2.tasks.initd import AddEC2InitScripts from bootstrapvz.providers.ec2.tasks.initd import AddEC2InitScripts
import logging
import os import os
import logging
log = logging.getLogger(__name__)
class AddSudoPackage(Task): class AddSudoPackage(Task):
@ -44,85 +45,66 @@ class PasswordlessSudo(Task):
os.chmod(sudo_admin_path, ug_read_only) os.chmod(sudo_admin_path, ug_read_only)
class AdminUserCredentialsPassword(Task): class AdminUserPassword(Task):
description = 'Set up access credentials for the admin user with a given password' description = 'Setting the admin user password'
phase = phases.system_modification phase = phases.system_modification
predecessors = [InstallInitScripts, CreateAdminUser] predecessors = [InstallInitScripts, CreateAdminUser]
@classmethod @classmethod
def run(cls, info): def run(cls, info):
from bootstrapvz.common.tools import log_check_call from bootstrapvz.common.tools import log_check_call
log = logging.getLogger(__name__) log_check_call(['chroot', info.root, 'chpasswd'],
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']['username'] +
':' + info.manifest.plugins['admin_user']['password'] ':' + info.manifest.plugins['admin_user']['password'])
)
return
class AdminUserCredentialsPublicKey(Task): class AdminUserPublicKey(Task):
description = 'Set up access credentials for the admin user with a given public key' description = 'Installing the public key for the admin user'
phase = phases.system_modification phase = phases.system_modification
predecessors = [AddEC2InitScripts, CreateAdminUser] predecessors = [AddEC2InitScripts, CreateAdminUser]
successors = [InstallInitScripts] successors = [InstallInitScripts]
@classmethod @classmethod
def run(cls, info): 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']: if 'ec2-get-credentials' in info.initd['install']:
log.warn( log.warn('You are using a static public key for the admin account.'
'You are using a static public key for the admin account.' 'This will conflict with the ec2 public key injection mechanism.'
' This will conflict with the ec2 public key injection mechanisn.' 'The ec2-get-credentials startup script will therefore not be enabled.')
' The ec2-get-credentials startup script has therefore been disabled.')
del info.initd['install']['ec2-get-credentials'] del info.initd['install']['ec2-get-credentials']
# Get the stuff we need (username & public key)
username = info.manifest.plugins['admin_user']['username'] username = info.manifest.plugins['admin_user']['username']
with open(info.manifest.plugins['admin_user']['pubkey']) as pubkey_handle:
pubkey = pubkey_handle.read()
ssh_file = os.path.join('/home', username, '.ssh/authorized_keys') # Create the ssh dir if nobody has created it yet
rel_ssh_file = os.path.join(info.root, 'home', username, '.ssh/authorized_keys') ssh_dir = os.path.join('/home', username, '.ssh')
if not os.path.exists(ssh_dir):
os.mkdir(ssh_dir, 0700)
ssh_dir = os.path.dirname(ssh_file) # Create (or append to) the authorized keys file (and chmod u=rw,go=)
rel_ssh_dir = os.path.dirname(rel_ssh_file) import stat
if not os.path.exists(rel_ssh_dir): auth_keys_abs = os.path.join(info.root, 'home', username, '.ssh/authorized_keys')
log.debug('Creating %s.' % rel_ssh_dir) with open(auth_keys_abs, 'a') as auth_keys_handle:
os.mkdir(rel_ssh_dir) auth_keys_handle.write(pubkey + '\n')
os.chmod(auth_keys_abs, (stat.S_IRUSR | stat.S_IWUSR))
log.debug('setting %s mode 700' % rel_ssh_dir) # Set the owner of the authorized keys file
mode = (stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR) # (must be through chroot, the host system doesn't know about the user)
os.chmod(rel_ssh_dir, mode) from bootstrapvz.common.tools import log_check_call
auth_keys_rel = os.path.join(ssh_dir, 'authorized_keys')
copy(full_path, rel_ssh_file) log_check_call(['chroot', info.root,
'chown', '-R', username, auth_keys_rel])
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): class AdminUserPublicKeyEC2(Task):
description = 'Set up access credentials for the admin user using the EC2 credentials' description = 'Modifying ec2-get-credentials to copy the ssh public key to the admin user'
phase = phases.system_modification phase = phases.system_modification
predecessors = [InstallInitScripts, CreateAdminUser] predecessors = [InstallInitScripts, CreateAdminUser]
@classmethod @classmethod
def run(cls, info): def run(cls, info):
from bootstrapvz.common.tools import sed_i from bootstrapvz.common.tools import sed_i
log = logging.getLogger(__name__)
getcreds_path = os.path.join(info.root, 'etc/init.d/ec2-get-credentials') 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'] username = info.manifest.plugins['admin_user']['username']
sed_i(getcreds_path, "username='root'", sed_i(getcreds_path, "username='root'", "username='{username}'".format(username=username))
"username='{username}'".format(username=username))