"""
Users
=====
"""
from pipes import quote
import posixpath
import random
import string
from fabric.api import hide, run, settings, sudo, local
from fabtools.group import (
exists as _group_exists,
create as _group_create,
)
from fabtools.files import uncommented_lines
from fabtools.utils import run_as_root
[docs]def exists(name):
"""
Check if a user exists.
"""
with settings(hide('running', 'stdout', 'warnings'), warn_only=True):
return run('getent passwd %(name)s' % locals()).succeeded
_SALT_CHARS = string.ascii_letters + string.digits + './'
def _crypt_password(password):
from crypt import crypt
random.seed()
salt = ''
for _ in range(2):
salt += random.choice(_SALT_CHARS)
crypted_password = crypt(password, salt)
return crypted_password
[docs]def create(name, comment=None, home=None, create_home=None, skeleton_dir=None,
group=None, create_group=True, extra_groups=None, password=None,
system=False, shell=None, uid=None, ssh_public_keys=None,
non_unique=False):
"""
Create a new user and its home directory.
If *create_home* is ``None`` (the default), a home directory will be
created for normal users, but not for system users.
You can override the default behaviour by setting *create_home* to
``True`` or ``False``.
If *system* is ``True``, the user will be a system account. Its UID
will be chosen in a specific range, and it will not have a home
directory, unless you explicitely set *create_home* to ``True``.
If *shell* is ``None``, the user's login shell will be the system's
default login shell (usually ``/bin/sh``).
*ssh_public_keys* can be a (local) filename or a list of (local)
filenames of public keys that should be added to the user's SSH
authorized keys (see :py:func:`fabtools.user.add_ssh_public_keys`).
Example::
import fabtools
if not fabtools.user.exists('alice'):
fabtools.user.create('alice')
with cd('/home/alice'):
# ...
"""
# Note that we use useradd (and not adduser), as it is the most
# portable command to create users across various distributions:
# http://refspecs.linuxbase.org/LSB_4.1.0/LSB-Core-generic/LSB-Core-generic/useradd.html
args = []
if comment:
args.append('-c %s' % quote(comment))
if home:
args.append('-d %s' % quote(home))
if group:
args.append('-g %s' % quote(group))
if create_group:
if not _group_exists(group):
_group_create(group)
if extra_groups:
groups = ','.join(quote(group) for group in extra_groups)
args.append('-G %s' % groups)
if create_home is None:
create_home = not system
if create_home is True:
args.append('-m')
elif create_home is False:
args.append('-M')
if skeleton_dir:
args.append('-k %s' % quote(skeleton_dir))
if password:
crypted_password = _crypt_password(password)
args.append('-p %s' % quote(crypted_password))
if system:
args.append('-r')
if shell:
args.append('-s %s' % quote(shell))
if uid:
args.append('-u %s' % uid)
if non_unique:
args.append('-o')
args.append(name)
args = ' '.join(args)
run_as_root('useradd %s' % args)
if ssh_public_keys:
if isinstance(ssh_public_keys, basestring):
ssh_public_keys = [ssh_public_keys]
add_ssh_public_keys(name, ssh_public_keys)
[docs]def modify(name, comment=None, home=None, move_current_home=False, group=None,
extra_groups=None, login_name=None, password=None, shell=None,
uid=None, ssh_public_keys=None, non_unique=False):
"""
Modify an existing user.
*ssh_public_keys* can be a (local) filename or a list of (local)
filenames of public keys that should be added to the user's SSH
authorized keys (see :py:func:`fabtools.user.add_ssh_public_keys`).
Example::
import fabtools
if fabtools.user.exists('alice'):
fabtools.user.modify('alice', shell='/bin/sh')
"""
args = []
if comment:
args.append('-c %s' % quote(comment))
if home:
args.append('-d %s' % quote(home))
if move_current_home:
args.append('-m')
if group:
args.append('-g %s' % quote(group))
if extra_groups:
groups = ','.join(quote(group) for group in extra_groups)
args.append('-G %s' % groups)
if login_name:
args.append('-l %s' % quote(login_name))
if password:
crypted_password = _crypt_password(password)
args.append('-p %s' % quote(crypted_password))
if shell:
args.append('-s %s' % quote(shell))
if uid:
args.append('-u %s' % quote(uid))
if non_unique:
args.append('-o')
if args:
args.append(name)
args = ' '.join(args)
run_as_root('usermod %s' % args)
if ssh_public_keys:
if isinstance(ssh_public_keys, basestring):
ssh_public_keys = [ssh_public_keys]
add_ssh_public_keys(name, ssh_public_keys)
[docs]def home_directory(name):
"""
Get the absolute path to the user's home directory
Example::
import fabtools
home = fabtools.user.home_directory('alice')
"""
with settings(hide('running', 'stdout')):
return run('echo ~' + name)
[docs]def local_home_directory(name=''):
"""
Get the absolute path to the local user's home directory
Example::
import fabtools
local_home = fabtools.user.local_home_directory()
"""
with settings(hide('running', 'stdout')):
return local('echo ~' + name, capture=True)
[docs]def authorized_keys(name):
"""
Get the list of authorized SSH public keys for the user
"""
ssh_dir = posixpath.join(home_directory(name), '.ssh')
authorized_keys_filename = posixpath.join(ssh_dir, 'authorized_keys')
return uncommented_lines(authorized_keys_filename, use_sudo=True)
[docs]def add_ssh_public_key(name, filename):
"""
Add a public key to the user's authorized SSH keys.
*filename* must be the local filename of a public key that should be
added to the user's SSH authorized keys.
Example::
import fabtools
fabtools.user.add_ssh_public_key('alice', '~/.ssh/id_rsa.pub')
"""
add_ssh_public_keys(name, [filename])
[docs]def add_ssh_public_keys(name, filenames):
"""
Add multiple public keys to the user's authorized SSH keys.
*filenames* must be a list of local filenames of public keys that
should be added to the user's SSH authorized keys.
Example::
import fabtools
fabtools.user.add_ssh_public_keys('alice', [
'~/.ssh/id1_rsa.pub',
'~/.ssh/id2_rsa.pub',
])
"""
from fabtools.require.files import (
directory as _require_directory,
file as _require_file,
)
ssh_dir = posixpath.join(home_directory(name), '.ssh')
_require_directory(ssh_dir, mode='700', owner=name, use_sudo=True)
authorized_keys_filename = posixpath.join(ssh_dir, 'authorized_keys')
_require_file(authorized_keys_filename, mode='600', owner=name,
use_sudo=True)
for filename in filenames:
with open(filename) as public_key_file:
public_key = public_key_file.read().strip()
# we don't use fabric.contrib.files.append() as it's buggy
if public_key not in authorized_keys(name):
sudo('echo %s >>%s' % (quote(public_key),
quote(authorized_keys_filename)))
[docs]def add_host_keys(name, hostname):
"""
Add all public keys of a host to the user's SSH known hosts file
"""
from fabtools.require.files import (
directory as _require_directory,
file as _require_file,
)
ssh_dir = posixpath.join(home_directory(name), '.ssh')
_require_directory(ssh_dir, mode='700', owner=name, use_sudo=True)
known_hosts_filename = posixpath.join(ssh_dir, 'known_hosts')
_require_file(known_hosts_filename, mode='644', owner=name, use_sudo=True)
known_hosts = uncommented_lines(known_hosts_filename, use_sudo=True)
with hide('running', 'stdout'):
res = run('ssh-keyscan -t rsa,dsa %s 2>/dev/null' % hostname)
for host_key in res.splitlines():
if host_key not in known_hosts:
sudo('echo %s >>%s' % (quote(host_key),
quote(known_hosts_filename)))