mirror of
https://github.com/kakwa/ldapcherry
synced 2024-11-24 02:14:20 +01:00
Add docker container build of application to repo
This commit is contained in:
parent
856157af79
commit
dc44455d17
2
.dockerignore
Normal file
2
.dockerignore
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
tests/
|
||||||
|
run_test.sh
|
13
Dockerfile
Normal file
13
Dockerfile
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
FROM debian:stretch
|
||||||
|
|
||||||
|
ADD . /opt/
|
||||||
|
WORKDIR "/opt"
|
||||||
|
RUN apt update && \
|
||||||
|
apt install -y python-dev python-pip libldap2-dev libsasl2-dev libssl-dev && \
|
||||||
|
pip install -e /opt/ -r /opt/requirements-stretch.txt pycodestyle passlib coveralls configparser in_place && \
|
||||||
|
/usr/bin/python2 /opt/setup.py install
|
||||||
|
|
||||||
|
VOLUME /etc/ldapcherry
|
||||||
|
EXPOSE 8080
|
||||||
|
|
||||||
|
CMD ["/usr/bin/python2", "/opt/init.py"]
|
102
README.rst
102
README.rst
@ -1,5 +1,5 @@
|
|||||||
**************
|
**************
|
||||||
LdapCherry
|
LdapCherry
|
||||||
**************
|
**************
|
||||||
|
|
||||||
.. image:: https://raw.githubusercontent.com/kakwa/ldapcherry/master/resources/static/img/apple-touch-icon-72-precomposed.png
|
.. image:: https://raw.githubusercontent.com/kakwa/ldapcherry/master/resources/static/img/apple-touch-icon-72-precomposed.png
|
||||||
@ -8,10 +8,10 @@ Nice and simple application to manage users and groups in multiple directory ser
|
|||||||
|
|
||||||
.. image:: https://travis-ci.org/kakwa/ldapcherry.svg?branch=master
|
.. image:: https://travis-ci.org/kakwa/ldapcherry.svg?branch=master
|
||||||
:target: https://travis-ci.org/kakwa/ldapcherry
|
:target: https://travis-ci.org/kakwa/ldapcherry
|
||||||
|
|
||||||
.. image:: https://coveralls.io/repos/kakwa/ldapcherry/badge.svg
|
.. image:: https://coveralls.io/repos/kakwa/ldapcherry/badge.svg
|
||||||
:target: https://coveralls.io/r/kakwa/ldapcherry
|
:target: https://coveralls.io/r/kakwa/ldapcherry
|
||||||
|
|
||||||
.. image:: https://img.shields.io/pypi/v/ldapcherry.svg
|
.. image:: https://img.shields.io/pypi/v/ldapcherry.svg
|
||||||
:target: https://pypi.python.org/pypi/ldapcherry
|
:target: https://pypi.python.org/pypi/ldapcherry
|
||||||
:alt: PyPI version
|
:alt: PyPI version
|
||||||
@ -88,7 +88,7 @@ The default backend plugins permit to manage Ldap and Active Directory.
|
|||||||
$ export SYSCONFDIR=/etc
|
$ export SYSCONFDIR=/etc
|
||||||
# change the directory where to put the resource (default: /usr/share)
|
# change the directory where to put the resource (default: /usr/share)
|
||||||
$ export DATAROOTDIR=/usr/share/
|
$ export DATAROOTDIR=/usr/share/
|
||||||
|
|
||||||
# install ldapcherry
|
# install ldapcherry
|
||||||
$ python setup.py install
|
$ python setup.py install
|
||||||
|
|
||||||
@ -100,6 +100,100 @@ The default backend plugins permit to manage Ldap and Active Directory.
|
|||||||
# launch ldapcherry
|
# launch ldapcherry
|
||||||
$ ldapcherryd -c /etc/ldapcherry/ldapcherry.ini -D
|
$ ldapcherryd -c /etc/ldapcherry/ldapcherry.ini -D
|
||||||
|
|
||||||
|
**********
|
||||||
|
Docker
|
||||||
|
**********
|
||||||
|
|
||||||
|
Building and running
|
||||||
|
^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
.. sourcecode:: bash
|
||||||
|
|
||||||
|
# Build the docker container with the tag ldapcherry
|
||||||
|
$ docker build -t ldapcherry .
|
||||||
|
|
||||||
|
# Run the docker container tagged as ldapcherry with the demo backend
|
||||||
|
# and allow incoming requests on port 8080 on the localhost
|
||||||
|
$ docker run -p 8080:8080 ldapcherry
|
||||||
|
|
||||||
|
Default environment variables
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
+-----------------------------+-------------------------------------+-----------------------+-------------------------+
|
||||||
|
| Environment Variable Name | Description | Default | Values |
|
||||||
|
+=============================+=====================================+=======================+=========================+
|
||||||
|
| ``DEBUG`` | Run the container in debug mode | ``False`` | * ``True`` |
|
||||||
|
| | | | * ``False`` |
|
||||||
|
+-----------------------------+-------------------------------------+-----------------------+-------------------------+
|
||||||
|
| ``SUFFIX`` | Set the suffix for the domain | ``dc=example,dc=org`` | * ``example.org`` |
|
||||||
|
| | | | * ``dc=example,dc=org`` |
|
||||||
|
+-----------------------------+-------------------------------------+-----------------------+-------------------------+
|
||||||
|
| ``SERVER_SOCKET_HOST`` | IP address for the daemon to run on | ``0.0.0.0`` | IP Address |
|
||||||
|
+-----------------------------+-------------------------------------+-----------------------+-------------------------+
|
||||||
|
| ``SERVER_SOCKET_PORT`` | Port for the daemon to run on | ``8080`` | Unprivileged Port |
|
||||||
|
+-----------------------------+-------------------------------------+-----------------------+-------------------------+
|
||||||
|
| ``LOG_ACCESS_HANDLER`` | The target for the access logs | ``stdout`` | * ``stdout`` |
|
||||||
|
| | | | * ``file`` |
|
||||||
|
| | | | * ``syslog`` |
|
||||||
|
| | | | * ``none`` |
|
||||||
|
+-----------------------------+-------------------------------------+-----------------------+-------------------------+
|
||||||
|
| ``LOG_ERROR_HANDLER`` | The target for the error logs | ``stdout`` | * ``stdout`` |
|
||||||
|
| | | | * ``file`` |
|
||||||
|
| | | | * ``syslog`` |
|
||||||
|
| | | | * ``none`` |
|
||||||
|
+-----------------------------+-------------------------------------+-----------------------+-------------------------+
|
||||||
|
|
||||||
|
.. warning::
|
||||||
|
|
||||||
|
Setting either of the ``LOG_<TYPE>_HANDLER`` variables to ``file`` requires the appropriate ``LOG_<TYPE>_FILE`` to be set
|
||||||
|
|
||||||
|
Other environment variables
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
All other confguration options are parsed programatically from environment variables that are formatted differently for the two file types -- one way for the ``ini`` file and another for the ``.yml`` file.
|
||||||
|
|
||||||
|
INI configuration file
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
The environment variables that should be passed to the ``ldapcherry.ini`` configuration file are only to be made into upper-case underscore-separated versions of the options inside of each section of the ldapcherry.ini file. For instance:
|
||||||
|
|
||||||
|
::
|
||||||
|
|
||||||
|
server.socket_host -> SERVER_SOCKET_HOST
|
||||||
|
request.show_tracebacks -> REQUEST_SHOW_TRACEBACKS
|
||||||
|
tools.sessions.timeout -> TOOLS_SESSIONS_TIMEOUT
|
||||||
|
min_length -> MIN_LENGTH
|
||||||
|
|
||||||
|
They will be put into their respective sections in the ldapcherry.ini file.
|
||||||
|
|
||||||
|
YAML configuration files
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
For the yaml configuration files (``attributes.yml`` and ``roles.yml``), the environment variable name is programatically parsed based on the following template:
|
||||||
|
|
||||||
|
::
|
||||||
|
|
||||||
|
<FILENAME (without the .yml extension)>__<ATTRIBUTE ID>__<PARAMETER>
|
||||||
|
|
||||||
|
The following example demonstrates how to customize the ``shell`` attribute ID in the ``attributes.yml`` file:
|
||||||
|
|
||||||
|
::
|
||||||
|
|
||||||
|
shell:
|
||||||
|
description: "Shell of the user"
|
||||||
|
display_name: "Shell"
|
||||||
|
weight: 80
|
||||||
|
values:
|
||||||
|
- /bin/bash
|
||||||
|
- /bin/zsh
|
||||||
|
- /bin/sh
|
||||||
|
|
||||||
|
::
|
||||||
|
|
||||||
|
ATTRIBUTES__SHELL__DESCRIPTION="Shell of the user"
|
||||||
|
ATTRIBUTES__SHELL__DISPLAY_NAME="Shell"
|
||||||
|
ATTRIBUTES__SHELL__WEIGHT="80"
|
||||||
|
ATTRIBUTES__SHELL__VALUES="['/bin/bash', '/bin/zsh', '/bin/sh']"
|
||||||
|
|
||||||
***********
|
***********
|
||||||
License
|
License
|
||||||
|
@ -11,7 +11,7 @@ server.socket_port = 8080
|
|||||||
|
|
||||||
# number of threads
|
# number of threads
|
||||||
server.thread_pool = 8
|
server.thread_pool = 8
|
||||||
#don't show traceback on error
|
# don't show traceback on error
|
||||||
request.show_tracebacks = False
|
request.show_tracebacks = False
|
||||||
|
|
||||||
# log configuration
|
# log configuration
|
||||||
|
577
init.py
Executable file
577
init.py
Executable file
@ -0,0 +1,577 @@
|
|||||||
|
#!/usr/bin/env python2
|
||||||
|
|
||||||
|
import os
|
||||||
|
import re
|
||||||
|
import ast
|
||||||
|
import sys
|
||||||
|
import yaml
|
||||||
|
import in_place
|
||||||
|
import itertools
|
||||||
|
import subprocess
|
||||||
|
import configparser
|
||||||
|
|
||||||
|
#
|
||||||
|
# This script sets up the ldapcherry config files through environment
|
||||||
|
# variables that are passed at startup time.
|
||||||
|
#
|
||||||
|
# The environment variables are only to be made into upper-case underscore-
|
||||||
|
# separated versions of the options inside of each section of the
|
||||||
|
# ldapcherry.ini file. For instance:
|
||||||
|
#
|
||||||
|
# server.socket_host -> SERVER_SOCKET_HOST
|
||||||
|
# request.show_tracebacks -> REQUEST_SHOW_TRACEBACKS
|
||||||
|
# tools.sessions.timeout -> TOOLS_SESSIONS_TIMEOUT
|
||||||
|
# min_length -> MIN_LENGTH
|
||||||
|
#
|
||||||
|
# They will be put into their respective sections in the ldapcherry.ini file.
|
||||||
|
#
|
||||||
|
# For the yaml configuration files, they are passed as follows:
|
||||||
|
#
|
||||||
|
# shell:
|
||||||
|
# description: "Shell of the user"
|
||||||
|
# display_name: "Shell"
|
||||||
|
# weight: 80
|
||||||
|
# values:
|
||||||
|
# - /bin/bash
|
||||||
|
# - /bin/zsh
|
||||||
|
# - /bin/sh
|
||||||
|
#
|
||||||
|
# ATTRIBUTES__SHELL__DESCRIPTION="Shell of the user"
|
||||||
|
# ATTRIBUTES__SHELL__DISPLAY_NAME="Shell"
|
||||||
|
# ATTRIBUTES__SHELL__WEIGHT="80"
|
||||||
|
# ATTRIBUTES__SHELL__VALUES="['/bin/bash', '/bin/zsh', '/bin/sh']"
|
||||||
|
#
|
||||||
|
|
||||||
|
#
|
||||||
|
# Sane defaults for docker.
|
||||||
|
#
|
||||||
|
ldapcherry_default_env_vars = {
|
||||||
|
'SERVER_SOCKET_HOST': "'0.0.0.0'",
|
||||||
|
'SERVER_SOCKET_PORT': '8080',
|
||||||
|
'LOG_ACCESS_HANDLER': "'stdout'",
|
||||||
|
'LOG_ERROR_HANDLER': "'stdout'",
|
||||||
|
}
|
||||||
|
|
||||||
|
def exclude_ini_backend(backend, config):
|
||||||
|
"""
|
||||||
|
Remove all options in the ini file associated with the specified backend
|
||||||
|
|
||||||
|
@str backend: the backend to exclude
|
||||||
|
@dict config: the configuration
|
||||||
|
"""
|
||||||
|
# Put all of the entries in the backend to exclude into a list in order to
|
||||||
|
# avoid removing items from the object that we are iterating over.
|
||||||
|
for option in list(config['backends']):
|
||||||
|
if option.startswith(backend):
|
||||||
|
config.remove_option('backends', option)
|
||||||
|
|
||||||
|
return config
|
||||||
|
|
||||||
|
|
||||||
|
def exclude_yaml_backend(backend, config, conf_file_name):
|
||||||
|
"""
|
||||||
|
Remove all options in the yaml file associated with the specified backend
|
||||||
|
|
||||||
|
@str backend: the backend to exclude
|
||||||
|
@dict config: the configuration
|
||||||
|
@str conf_file_name: the name of the configuration file
|
||||||
|
"""
|
||||||
|
backends = {'roles': 'backends_groups', 'attributes': 'backends'}
|
||||||
|
for top_level in config:
|
||||||
|
try:
|
||||||
|
config[top_level][backends[conf_file_name]].pop(backend)
|
||||||
|
except KeyError:
|
||||||
|
continue
|
||||||
|
|
||||||
|
return config
|
||||||
|
|
||||||
|
|
||||||
|
def include_exclude_yaml_backends(config, conf_file_name, possible_backends, used_backends):
|
||||||
|
"""
|
||||||
|
Determine which backends are in play, and implement sane defaults
|
||||||
|
|
||||||
|
@dict config: the configuration
|
||||||
|
@str conf_file_name: the name of the configuration file
|
||||||
|
@list possible_backends: the backends that can be used
|
||||||
|
@list used_backends: the backends that are being used
|
||||||
|
"""
|
||||||
|
if len(used_backends) == 0:
|
||||||
|
if conf_file_name == 'roles':
|
||||||
|
for role in config:
|
||||||
|
demo_bg = config[role]['backends_groups'].pop('ldap')
|
||||||
|
config[role]['backends_groups']['demo'] = demo_bg
|
||||||
|
config['admin-lv2'].pop('LC_admins')
|
||||||
|
|
||||||
|
#
|
||||||
|
# Set up the additional role 'sec-officer'
|
||||||
|
#
|
||||||
|
config['sec-officer'] = {}
|
||||||
|
config['sec-officer']['display_name'] = 'Security Officer'
|
||||||
|
config['sec-officer']['description'] = "Security officer"
|
||||||
|
config['sec-officer']['LC_admins'] = "True"
|
||||||
|
config['sec-officer']['backends_groups'] = {}
|
||||||
|
config['sec-officer']['backends_groups']['demo'] = ['SECOFF']
|
||||||
|
|
||||||
|
elif conf_file_name == 'attributes':
|
||||||
|
for attr in config:
|
||||||
|
attr_backend = config[attr]['backends'].pop('ldap')
|
||||||
|
config[attr]['backends']['demo'] = attr_backend
|
||||||
|
|
||||||
|
for backend in possible_backends:
|
||||||
|
if backend != 'demo':
|
||||||
|
config = exclude_yaml_backend(backend, config, conf_file_name)
|
||||||
|
|
||||||
|
else:
|
||||||
|
# Take out all of the backend options that are not in use
|
||||||
|
unused_backends = list(set(possible_backends) - set(used_backends))
|
||||||
|
for backend in unused_backends:
|
||||||
|
config = exclude_yaml_backend(backend, config, conf_file_name)
|
||||||
|
|
||||||
|
return config
|
||||||
|
|
||||||
|
|
||||||
|
def get_backends(all_ini_settings):
|
||||||
|
"""
|
||||||
|
Get both all of the possible backends, and the ones that are actually being
|
||||||
|
used
|
||||||
|
|
||||||
|
@dict all_ini_settings: all of the settings possible in the ini file
|
||||||
|
"""
|
||||||
|
# We're going to check the environment variables to see what was passed in.
|
||||||
|
# This is assuming that there must be a URI for whichever backend is
|
||||||
|
# dynamically passed in. We could parse the config to find out what is
|
||||||
|
# defined, but we need to interpret what the container was launched to
|
||||||
|
# connect to, which is likely what was passed in via environment vars.
|
||||||
|
possible_backends = []
|
||||||
|
|
||||||
|
# Here I _could_ just say possible_backends = ['ldap', 'ad', 'demo'], but
|
||||||
|
# I want to make this as low-maintenance as possible. If there is another
|
||||||
|
# backend that becomes available, this will catch it as-is.
|
||||||
|
for option in all_ini_settings['backends']:
|
||||||
|
possible_backend = option.split('.')[0]
|
||||||
|
if (possible_backend not in possible_backends and
|
||||||
|
possible_backend != 'demo'):
|
||||||
|
possible_backends.append(possible_backend)
|
||||||
|
|
||||||
|
# Get all the backends that were passed in with environment vars.
|
||||||
|
used_backends = []
|
||||||
|
for backend in possible_backends:
|
||||||
|
backend_prefix = "{}_".format(backend.upper())
|
||||||
|
if any(env_var.startswith(backend_prefix) for env_var in os.environ):
|
||||||
|
used_backends.append(backend)
|
||||||
|
|
||||||
|
return possible_backends, used_backends
|
||||||
|
|
||||||
|
|
||||||
|
def include_exclude_ini_backends(config, possible_backends, used_backends,
|
||||||
|
all_ini_settings):
|
||||||
|
"""
|
||||||
|
Allow sane defaults to be applied to the intended backend, and remove
|
||||||
|
unnecessary defaults from the config to avoid backend errors.
|
||||||
|
|
||||||
|
@dict config: the configuration
|
||||||
|
@list possible_backends: the backends that can be used
|
||||||
|
@list used_backends: the backends that are being used
|
||||||
|
@dict all_ini_settings: all of the settings possible in the ini file
|
||||||
|
"""
|
||||||
|
# Insert all of the demo settings if there were no backends specified
|
||||||
|
if len(used_backends) == 0:
|
||||||
|
# Add all of the demo settings in there.
|
||||||
|
for option in all_ini_settings['backends']:
|
||||||
|
if option.startswith('demo') and option not in config['backends']:
|
||||||
|
config['backends'][option] = all_ini_settings['backends'][option]
|
||||||
|
|
||||||
|
# Set the admin user's security group to allow for all admin actions
|
||||||
|
config['backends']['demo.admin.groups'] = "'SECOFF'"
|
||||||
|
|
||||||
|
# Remove all other options for all other backends
|
||||||
|
for backend in possible_backends:
|
||||||
|
if backend != 'demo':
|
||||||
|
config = exclude_ini_backend(backend, config)
|
||||||
|
|
||||||
|
else:
|
||||||
|
# Take out all of the backend options that are not in use
|
||||||
|
unused_backends = list(set(possible_backends) - set(used_backends))
|
||||||
|
for backend in unused_backends:
|
||||||
|
config = exclude_ini_backend(backend, config)
|
||||||
|
|
||||||
|
return config
|
||||||
|
|
||||||
|
|
||||||
|
def get_config_value(env_var):
|
||||||
|
"""
|
||||||
|
Format valid configuration values based on the string that is passed
|
||||||
|
|
||||||
|
@str env_var: the environment var that needs to be formatted
|
||||||
|
"""
|
||||||
|
val = os.getenv(env_var)
|
||||||
|
|
||||||
|
if val is not None:
|
||||||
|
if val.isdigit():
|
||||||
|
return val
|
||||||
|
if val in ['True', 'False']:
|
||||||
|
return val
|
||||||
|
# Handle when we're not passed variables that are properly quoted
|
||||||
|
elif (val.startswith("'") and not val.endswith("'")) or "'" in val:
|
||||||
|
return "\"{}\"".format(val)
|
||||||
|
elif (val.startswith('"') and not val.endswith('"')) or '"' in val:
|
||||||
|
return "'{}'".format(val)
|
||||||
|
else:
|
||||||
|
return "'{}'".format(val)
|
||||||
|
elif env_var in ldapcherry_default_env_vars:
|
||||||
|
return ldapcherry_default_env_vars[env_var]
|
||||||
|
else:
|
||||||
|
# This is most likely a blank string
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def set_yaml_override(env_var, config, env_var_list):
|
||||||
|
"""
|
||||||
|
Recursive function to ensure the creation of the appropriate hierarchy in
|
||||||
|
the yaml list.
|
||||||
|
|
||||||
|
@str env_var: the environment var that needs to be formatted
|
||||||
|
@dict config: the configuration
|
||||||
|
@list env_var_list: the list of all present environment vars
|
||||||
|
"""
|
||||||
|
|
||||||
|
if env_var_list[0] in config and len(env_var_list) > 1:
|
||||||
|
# The key exists, but there is more hierarchy to be traversed
|
||||||
|
set_yaml_override(env_var, config[env_var_list[0]], env_var_list[1:])
|
||||||
|
elif env_var_list[0] not in config and len(env_var_list) > 1:
|
||||||
|
# The key isn't already in the config data structure, so create it as a
|
||||||
|
# new dictionary, as there is still more hierarchy to be traversed
|
||||||
|
config[env_var_list[0]] = {}
|
||||||
|
set_yaml_override(env_var, config[env_var_list[0]], env_var_list[1:])
|
||||||
|
else:
|
||||||
|
# This is the last key, therefore we should insert the value here.
|
||||||
|
if get_config_value(env_var).startswith(('{', '[')):
|
||||||
|
config[env_var_list[0]] = ast.literal_eval(get_config_value(env_var))
|
||||||
|
# We need to remove the value if it's pass into the container as a zero
|
||||||
|
# length string
|
||||||
|
elif not os.getenv(env_var):
|
||||||
|
config.pop(env_var_list[0])
|
||||||
|
else:
|
||||||
|
config[env_var_list[0]] = get_config_value(env_var)
|
||||||
|
|
||||||
|
return config
|
||||||
|
|
||||||
|
|
||||||
|
def find_yaml_override(config, env_var):
|
||||||
|
"""
|
||||||
|
Find an environment variable that overrides a setting in a yaml
|
||||||
|
configuration file
|
||||||
|
|
||||||
|
@dict config: the configuration
|
||||||
|
@str env_var: the environment var that needs to be formatted
|
||||||
|
"""
|
||||||
|
# Split the env_var into a list delineated by double underscores
|
||||||
|
env_var_list = env_var.lower().split('__')[1:]
|
||||||
|
|
||||||
|
# Keeping in mind that we can have any type of definitions here that can be
|
||||||
|
# typed out at the _root_ level, need to have a recursive function
|
||||||
|
config = set_yaml_override(env_var, config, env_var_list)
|
||||||
|
|
||||||
|
return config
|
||||||
|
|
||||||
|
|
||||||
|
def find_ini_override(env_var, config, all_ini_settings):
|
||||||
|
"""
|
||||||
|
Find an environment variable that overrides a setting in a yaml
|
||||||
|
configuration file
|
||||||
|
|
||||||
|
@str env_var: the environment var that needs to be formatted
|
||||||
|
@dict config: the configuration
|
||||||
|
@dict all_ini_settings: all of the settings possible in the ini file
|
||||||
|
"""
|
||||||
|
# Here we split the env_var into a list. We are going to be using
|
||||||
|
# env vars that are the same as the objects, but all caps and that
|
||||||
|
# have underscores instead of periods, but split at the same
|
||||||
|
# places. If we split the option at all of the periods as well as
|
||||||
|
# the underscores, and compare that to a similarly split env var
|
||||||
|
# that has been lower-cased, we should have an accurate comparison.
|
||||||
|
env_var_list = env_var.lower().split('_')
|
||||||
|
|
||||||
|
# Quick fix for otherwise sane defaults, but options that have the
|
||||||
|
# incorrect suffix. We want to fix that here.
|
||||||
|
if env_var == 'SUFFIX':
|
||||||
|
suffix = os.getenv(env_var)
|
||||||
|
for section in config.sections():
|
||||||
|
for option in config[section]:
|
||||||
|
setting = config.get(section, option)
|
||||||
|
if 'example' in setting:
|
||||||
|
# We don't have to handle `example.org` here, since:
|
||||||
|
#
|
||||||
|
# 1. It doesn't exist in `ldapcherry.ini`
|
||||||
|
# 2. The suffix of an LDAP hierarchy is always in the
|
||||||
|
# regular `dc=something,dc=TLD` format. We shouldn't
|
||||||
|
# confuse users by accepting `example.org` here as
|
||||||
|
# that is technically not an LDAP suffix, but rather
|
||||||
|
# a _domain_.
|
||||||
|
new_setting = setting.replace('dc=example,dc=org', suffix)
|
||||||
|
config[section][option] = new_setting
|
||||||
|
# We return the config here and end this execution of this function
|
||||||
|
# since none of the other settings will be affected by the suffix. Or,
|
||||||
|
# if so, the suffix will already be baked into the value of the env var
|
||||||
|
# that is being passed.
|
||||||
|
return config
|
||||||
|
|
||||||
|
# Compare every setting to the env_var_list by splitting the option at all
|
||||||
|
# of the periods as well as the underscores, and compare that to the
|
||||||
|
# similarly split env var that has been lower-cased. This should cause us
|
||||||
|
# to have an accurate comparison.
|
||||||
|
for section in all_ini_settings:
|
||||||
|
for setting in all_ini_settings[section]:
|
||||||
|
option_list = setting.replace('.', ' ').replace('_', ' ').split()
|
||||||
|
if env_var_list == option_list:
|
||||||
|
# Remove the value if it's passed as an empty string
|
||||||
|
if not get_config_value(env_var):
|
||||||
|
config.remove_option(section, setting)
|
||||||
|
else:
|
||||||
|
config[section][setting] = get_config_value(env_var)
|
||||||
|
return config
|
||||||
|
|
||||||
|
# Here we haven't found a match against either the existing settings, or
|
||||||
|
# the commented-out settings, so we are going to just return the config
|
||||||
|
# as-is. This most likely means that we've evaluated an env var that was
|
||||||
|
# not meant for this program, but that the system uses in some other
|
||||||
|
# capacity.
|
||||||
|
return config
|
||||||
|
|
||||||
|
|
||||||
|
def all_yaml_settings(yaml_filepath):
|
||||||
|
"""
|
||||||
|
Uncomment all options in the yaml file
|
||||||
|
|
||||||
|
@str yaml_filepath: the path to the yaml configuration file
|
||||||
|
"""
|
||||||
|
# Uncomment the alternate backends to make the defaults available for
|
||||||
|
# later
|
||||||
|
with in_place.InPlace(yaml_filepath) as commented_file:
|
||||||
|
last_line_was_commented = False
|
||||||
|
for line in commented_file:
|
||||||
|
# Our rationale here is that we want to catch all of the top-level keys
|
||||||
|
# in the yaml structure that are commented out and discard them and all
|
||||||
|
# of their subsequent settings. The top-level key - if commented out -
|
||||||
|
# will start with a comment ('#') and immediately be followed by a
|
||||||
|
# character. Anything with a space will either have a parent all the
|
||||||
|
# way back to the root top-level key, or be an option that needs to be
|
||||||
|
# uncommented (like '# ad: unicodePWd') and dealt with later on in
|
||||||
|
# the script. These options (at the point of writing) are only
|
||||||
|
# different backends that will be taken care of in a separate function.
|
||||||
|
if not last_line_was_commented and line.startswith('# '):
|
||||||
|
line = line.replace('#', '')
|
||||||
|
commented_file.write(line)
|
||||||
|
last_line_was_commented = False
|
||||||
|
continue
|
||||||
|
elif line.startswith('#'):
|
||||||
|
last_line_was_commented = True
|
||||||
|
continue
|
||||||
|
else:
|
||||||
|
last_line_was_commented = False
|
||||||
|
commented_file.write(line)
|
||||||
|
|
||||||
|
|
||||||
|
def get_all_ini_settings(filelines):
|
||||||
|
"""
|
||||||
|
Unless ldapcherry is going to maintain a prettily formatted list of all
|
||||||
|
of the possible configuration options, or this script can be guaranteed to
|
||||||
|
be updated with all possible options, the best way to gather all of them is
|
||||||
|
to parse them from the provided sample configuration file.
|
||||||
|
|
||||||
|
While this may seem a little hacky, it is perfectly relevant to a
|
||||||
|
configuration file that contains:
|
||||||
|
|
||||||
|
- Commented-out options
|
||||||
|
- Commented-out sections
|
||||||
|
- Duplicate options in the comments
|
||||||
|
- Mutually exclusive options
|
||||||
|
- Options that have no periods in their name
|
||||||
|
- Options that have one or more periods in their name
|
||||||
|
- Options whose default includes "example.com" setups
|
||||||
|
- Assumed sane defaults
|
||||||
|
|
||||||
|
This thoroughly prevents any sort of sane parsing without a great deal of
|
||||||
|
manipulation. It is easier to hold all available settings in limbo able to
|
||||||
|
be tested against than to try to interpret them in a way that could
|
||||||
|
potentially be ambiguous in deployment scenarios.
|
||||||
|
|
||||||
|
@list filelines: the lines in the configuration file
|
||||||
|
"""
|
||||||
|
# Create a dict to hold the settings
|
||||||
|
all_ini_settings = {}
|
||||||
|
|
||||||
|
#
|
||||||
|
# Get all possible settings
|
||||||
|
#
|
||||||
|
for fileline in filelines:
|
||||||
|
# Retrieve and store the section location for reference later
|
||||||
|
if fileline.startswith('[') or fileline.startswith('#['):
|
||||||
|
section = fileline.replace('[', '').replace(']', '').replace('#', '').strip()
|
||||||
|
if section not in all_ini_settings:
|
||||||
|
all_ini_settings[section] = {}
|
||||||
|
continue
|
||||||
|
elif '=' in fileline:
|
||||||
|
# Get the option name
|
||||||
|
option = fileline.strip().split()[0]
|
||||||
|
if option.startswith('#'):
|
||||||
|
# This is a commented out option, so we strip the preceeding
|
||||||
|
# hash
|
||||||
|
option = option[1:]
|
||||||
|
setting_index = fileline.index("=")
|
||||||
|
# Take a slice of the string right after the index where we find
|
||||||
|
# the first equals sign, and then get rid of any leading or
|
||||||
|
# trailing whitespace, leaving us with the value of the option.
|
||||||
|
setting = fileline[setting_index + 1:].strip()
|
||||||
|
# Check if the option already exists in the list of the options
|
||||||
|
if not option in all_ini_settings:
|
||||||
|
all_ini_settings[section][option] = setting
|
||||||
|
|
||||||
|
return all_ini_settings
|
||||||
|
|
||||||
|
|
||||||
|
def yaml_setup(yaml_filepath, possible_backends, used_backends):
|
||||||
|
"""
|
||||||
|
Handle yaml configuration file initialization if necessary
|
||||||
|
|
||||||
|
@str yaml_filepath: the path to the yaml configuration file
|
||||||
|
@list possible_backends: the backends that can be used
|
||||||
|
@list used_backends: the backends that are being used
|
||||||
|
"""
|
||||||
|
# Load the yaml
|
||||||
|
with open(yaml_filepath, 'r') as stream:
|
||||||
|
try:
|
||||||
|
config = yaml.safe_load(stream)
|
||||||
|
except yaml.YAMLError as e:
|
||||||
|
print(e)
|
||||||
|
raise SystemExit
|
||||||
|
|
||||||
|
# Since we're naming our environment vars beginning with the name
|
||||||
|
# that they belong to (ROLES, ATTRIBUTES) we can simply check to see if
|
||||||
|
# the env var we're currently looping over is applicable by just
|
||||||
|
# checking to see if the variable starts with the file name that we're
|
||||||
|
# currently working on.
|
||||||
|
conf_file_name = yaml_filepath.split('/')[-1].split('.')[0]
|
||||||
|
|
||||||
|
# We will, however, make sure to set the defaults for the demo backend.
|
||||||
|
config = include_exclude_yaml_backends(config, conf_file_name,
|
||||||
|
possible_backends, used_backends)
|
||||||
|
|
||||||
|
# We're not going to read all of the default backends or options since
|
||||||
|
# they are largely arbitrary, and realistically should be set up by the
|
||||||
|
# admin.
|
||||||
|
for env_var in itertools.chain(ldapcherry_default_env_vars, os.environ):
|
||||||
|
if env_var.split('__')[0].lower() == conf_file_name:
|
||||||
|
config = find_yaml_override(config, env_var)
|
||||||
|
|
||||||
|
# Write the file out
|
||||||
|
with open(yaml_filepath, 'w') as configfile:
|
||||||
|
yaml.dump(config, configfile, default_flow_style=False)
|
||||||
|
|
||||||
|
|
||||||
|
def ini_setup(all_ini_settings, possible_backends, used_backends):
|
||||||
|
"""
|
||||||
|
Handle ini configuration file initialization if necessary
|
||||||
|
|
||||||
|
@dict all_ini_settings: all of the settings possible in the ini file
|
||||||
|
@list possible_backends: the backends that can be used
|
||||||
|
@list used_backends: the backends that are being used
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Set up config parser
|
||||||
|
config = configparser.ConfigParser(interpolation=None)
|
||||||
|
config.read('/etc/ldapcherry/ldapcherry.ini')
|
||||||
|
|
||||||
|
# Ensure that there are no lingering backend defaults.
|
||||||
|
config = include_exclude_ini_backends(config, possible_backends,
|
||||||
|
used_backends, all_ini_settings)
|
||||||
|
|
||||||
|
# Set any objects with the ENV VAR value that overrides it.
|
||||||
|
# We pass the default env vars first because we definitely want those set,
|
||||||
|
# but also allow for the possibility of override later. A for loop's
|
||||||
|
# iteration order is controlled by whatever object it's iterating over.
|
||||||
|
# Iterating over an ordered collection like a list is guaranteed to iterate
|
||||||
|
# over elements in the list's order.
|
||||||
|
for env_var in itertools.chain(ldapcherry_default_env_vars, os.environ):
|
||||||
|
config = find_ini_override(env_var, config, all_ini_settings)
|
||||||
|
|
||||||
|
# Write the file out
|
||||||
|
with open('/etc/ldapcherry/ldapcherry.ini', 'w') as configfile:
|
||||||
|
config.write(configfile)
|
||||||
|
|
||||||
|
return possible_backends, used_backends
|
||||||
|
|
||||||
|
|
||||||
|
def conf_setup():
|
||||||
|
"""
|
||||||
|
Set up the ini configuration files if they have not been messed with. This
|
||||||
|
is in case the configuration file is being mounted in a docker volume
|
||||||
|
which should be considered to be already set up by the admin as opposed
|
||||||
|
to a vanilla deploy or another method that does not set the configuration
|
||||||
|
file up beforehand.
|
||||||
|
"""
|
||||||
|
#
|
||||||
|
# Set up the INI file
|
||||||
|
#
|
||||||
|
|
||||||
|
# Read the ini file
|
||||||
|
with open('/etc/ldapcherry/ldapcherry.ini', 'r') as file:
|
||||||
|
ini_filelines = file.readlines()
|
||||||
|
|
||||||
|
# Make the alternate ini settings available for use.
|
||||||
|
all_ini_settings = get_all_ini_settings(ini_filelines)
|
||||||
|
|
||||||
|
# Get the lists of possible and used backends
|
||||||
|
possible_backends, used_backends = get_backends(all_ini_settings)
|
||||||
|
|
||||||
|
# We're testing to see if there are any comments in the file since the
|
||||||
|
# configuration file as configured by this script has no comments in it.
|
||||||
|
if any(fileline.startswith('#') for fileline in ini_filelines):
|
||||||
|
ini_setup(all_ini_settings, possible_backends, used_backends)
|
||||||
|
|
||||||
|
#
|
||||||
|
# Similarly to the ini file, set up the roles and attributes YAML files.
|
||||||
|
# However, here we leave in the extra backends to be taken out later. This
|
||||||
|
# is because there are no duplicates, and yaml can parse this just fine,
|
||||||
|
# unlike the ini parser which will choke on it if they are all uncommented.
|
||||||
|
#
|
||||||
|
|
||||||
|
for yaml_file in ['roles.yml', 'attributes.yml']:
|
||||||
|
yaml_filepath = "/etc/ldapcherry/{}".format(yaml_file)
|
||||||
|
# Read the YAML file to test for commented-out lines below
|
||||||
|
with open(yaml_filepath, 'r') as file:
|
||||||
|
yaml_filelines = file.readlines()
|
||||||
|
|
||||||
|
# We're testing to see if there are any comments in the file since the
|
||||||
|
# configuration file as configured by this script has no comments in it.
|
||||||
|
if any(fileline.startswith('#') for fileline in yaml_filelines):
|
||||||
|
# Uncomment all of the yaml setting backends
|
||||||
|
all_yaml_settings(yaml_filepath)
|
||||||
|
|
||||||
|
# Configure the yaml conf file given the backends that we're using
|
||||||
|
yaml_setup(yaml_filepath, possible_backends, used_backends)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
conf_setup()
|
||||||
|
invocation = ["/usr/local/bin/ldapcherryd", "-c", "/etc/ldapcherry/ldapcherry.ini"]
|
||||||
|
try:
|
||||||
|
if ast.literal_eval(os.environ['DEBUG']):
|
||||||
|
invocation.append("-D")
|
||||||
|
except KeyError:
|
||||||
|
# The env var `DEBUG` was not specified one way or the other
|
||||||
|
pass
|
||||||
|
try:
|
||||||
|
subprocess.call(invocation)
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
raise SystemExit
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
#
|
||||||
|
# Handle ^C without throwing an exception
|
||||||
|
#
|
||||||
|
try:
|
||||||
|
main()
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
raise SystemExit
|
Loading…
Reference in New Issue
Block a user