File: //usr/lib/python2.7/site-packages/salt/auth/file.py
# -*- coding: utf-8 -*-
'''
Provide authentication using local files
.. versionadded:: 2018.3.0
The `file` auth module allows simple authentication via local files. Different
filetypes are supported, including:
1. Text files, with passwords in plaintext or hashed
2. Apache-style htpasswd files
3. Apache-style htdigest files
.. note::
The ``python-passlib`` library is required when using a ``^filetype`` of
``htpasswd`` or ``htdigest``.
The simplest example is a plaintext file with usernames and passwords:
.. code-block:: yaml
external_auth:
file:
^filename: /etc/insecure-user-list.txt
gene:
- .*
dean:
- test.*
In this example the ``/etc/insecure-user-list.txt`` file would be formatted
as so:
.. code-block:: text
dean:goneFishing
gene:OceanMan
``^filename`` is the only required parameter. Any parameter that begins with
a ``^`` is passed directly to the underlying file authentication function
via ``kwargs``, with the leading ``^`` being stripped.
The text file option is configurable to work with legacy formats:
.. code-block:: yaml
external_auth:
file:
^filename: /etc/legacy_users.txt
^filetype: text
^hashtype: md5
^username_field: 2
^password_field: 3
^field_separator: '|'
trey:
- .*
This would authenticate users against a file of the following format:
.. code-block:: text
46|trey|16a0034f90b06bf3c5982ed8ac41aab4
555|mike|b6e02a4d2cb2a6ef0669e79be6fd02e4
2001|page|14fce21db306a43d3b680da1a527847a
8888|jon|c4e94ba906578ccf494d71f45795c6cb
.. note::
The :py:func:`hashutil.digest <salt.modules.hashutil.digest>` execution
function is used for comparing hashed passwords, so any algorithm
supported by that function will work.
There is also support for Apache-style ``htpasswd`` and ``htdigest`` files:
.. code-block:: yaml
external_auth:
file:
^filename: /var/www/html/.htusers
^filetype: htpasswd
cory:
- .*
When using ``htdigest`` the ``^realm`` must be set:
.. code-block:: yaml
external_auth:
file:
^filename: /var/www/html/.htdigest
^filetype: htdigest
^realm: MySecureRealm
cory:
- .*
'''
# Import python libs
from __future__ import absolute_import, print_function, unicode_literals
import logging
import os
# Import salt utils
import salt.utils.files
import salt.utils.versions
log = logging.getLogger(__name__)
__virtualname__ = 'file'
def __virtual__():
return __virtualname__
def _get_file_auth_config():
'''
Setup defaults and check configuration variables for auth backends
'''
config = {
'filetype': 'text',
'hashtype': 'plaintext',
'field_separator': ':',
'username_field': 1,
'password_field': 2,
}
for opt in __opts__['external_auth'][__virtualname__]:
if opt.startswith('^'):
config[opt[1:]] = __opts__['external_auth'][__virtualname__][opt]
if 'filename' not in config:
log.error('salt.auth.file: An authentication file must be specified '
'via external_auth:file:^filename')
return False
if not os.path.exists(config['filename']):
log.error('salt.auth.file: The configured external_auth:file:^filename (%s)'
'does not exist on the filesystem', config['filename'])
return False
config['username_field'] = int(config['username_field'])
config['password_field'] = int(config['password_field'])
return config
def _text(username, password, **kwargs):
'''
The text file function can authenticate plaintext and digest methods
that are available in the :py:func:`hashutil.digest <salt.modules.hashutil.digest>`
function.
'''
filename = kwargs['filename']
hashtype = kwargs['hashtype']
field_separator = kwargs['field_separator']
username_field = kwargs['username_field']-1
password_field = kwargs['password_field']-1
with salt.utils.files.fopen(filename, 'r') as pwfile:
for line in pwfile.readlines():
fields = line.strip().split(field_separator)
try:
this_username = fields[username_field]
except IndexError:
log.error('salt.auth.file: username field (%s) does not exist '
'in file %s', username_field, filename)
return False
try:
this_password = fields[password_field]
except IndexError:
log.error('salt.auth.file: password field (%s) does not exist '
'in file %s', password_field, filename)
return False
if this_username == username:
if hashtype == 'plaintext':
if this_password == password:
return True
else:
# Exceptions for unknown hash types will be raised by hashutil.digest
if this_password == __salt__['hashutil.digest'](password, hashtype):
return True
# Short circuit if we've already found the user but the password was wrong
return False
return False
def _htpasswd(username, password, **kwargs):
'''
Provide authentication via Apache-style htpasswd files
'''
from passlib.apache import HtpasswdFile
pwfile = HtpasswdFile(kwargs['filename'])
# passlib below version 1.6 uses 'verify' function instead of 'check_password'
if salt.utils.versions.version_cmp(kwargs['passlib_version'], '1.6') < 0:
return pwfile.verify(username, password)
else:
return pwfile.check_password(username, password)
def _htdigest(username, password, **kwargs):
'''
Provide authentication via Apache-style htdigest files
'''
realm = kwargs.get('realm', None)
if not realm:
log.error('salt.auth.file: A ^realm must be defined in '
'external_auth:file for htdigest filetype')
return False
from passlib.apache import HtdigestFile
pwfile = HtdigestFile(kwargs['filename'])
# passlib below version 1.6 uses 'verify' function instead of 'check_password'
if salt.utils.versions.version_cmp(kwargs['passlib_version'], '1.6') < 0:
return pwfile.verify(username, realm, password)
else:
return pwfile.check_password(username, realm, password)
def _htfile(username, password, **kwargs):
'''
Gate function for _htpasswd and _htdigest authentication backends
'''
filetype = kwargs.get('filetype', 'htpasswd').lower()
try:
import passlib
kwargs['passlib_version'] = passlib.__version__
except ImportError:
log.error('salt.auth.file: The python-passlib library is required '
'for %s filetype', filetype)
return False
if filetype == 'htdigest':
return _htdigest(username, password, **kwargs)
else:
return _htpasswd(username, password, **kwargs)
FILETYPE_FUNCTION_MAP = {
'text': _text,
'htpasswd': _htfile,
'htdigest': _htfile
}
def auth(username, password):
'''
File based authentication
^filename
The path to the file to use for authentication.
^filetype
The type of file: ``text``, ``htpasswd``, ``htdigest``.
Default: ``text``
^realm
The realm required by htdigest authentication.
.. note::
The following parameters are only used with the ``text`` filetype.
^hashtype
The digest format of the password. Can be ``plaintext`` or any digest
available via :py:func:`hashutil.digest <salt.modules.hashutil.digest>`.
Default: ``plaintext``
^field_separator
The character to use as a delimiter between fields in a text file.
Default: ``:``
^username_field
The numbered field in the text file that contains the username, with
numbering beginning at 1 (one).
Default: ``1``
^password_field
The numbered field in the text file that contains the password, with
numbering beginning at 1 (one).
Default: ``2``
'''
config = _get_file_auth_config()
if not config:
return False
auth_function = FILETYPE_FUNCTION_MAP.get(config['filetype'], 'text')
return auth_function(username, password, **config)