Mini Shell
"""
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 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)
Zerion Mini Shell 1.0