A minimalistic OpenLDAP admin frontend for managing users and groups.
Go to file
2024-01-23 16:20:40 +00:00
docs fix: werkzeug import error preventing startup 2024-01-23 16:20:40 +00:00
lumi2 fix: werkzeug import error preventing startup 2024-01-23 16:20:40 +00:00
scss fix(usermanager): UI improvements 2022-12-03 21:40:05 +01:00
tests fix: werkzeug import error preventing startup 2024-01-23 16:20:40 +00:00
.dockerignore feat: set up waitress as production server 2022-12-05 15:59:43 +01:00
.gitignore feat(frontend): add custom bootstrap CSS 2022-12-02 01:26:40 +01:00
config.py feat(docs): add demo, configuration and deployment info 2022-12-03 21:39:02 +01:00
docker-compose.yml fix: rename misleading container name 2022-12-05 16:10:45 +01:00
Dockerfile fix(docs): remove outdated comment 2022-12-05 15:59:43 +01:00
LICENSE.md feat: add about page and license 2022-12-05 00:03:17 +01:00
package-lock.json fix: werkzeug import error preventing startup 2024-01-23 16:20:40 +00:00
package.json fix: werkzeug import error preventing startup 2024-01-23 16:20:40 +00:00
pytest.ini feat(tests): add pytest and write unit tests 2022-11-09 14:45:47 +01:00
README.md fix(ldap): replace SHA512 user passwords with SSHA 2022-12-05 15:58:33 +01:00
requirements.txt fix: werkzeug import error preventing startup 2024-01-23 16:20:40 +00:00
shell.nix fix: werkzeug import error preventing startup 2024-01-23 16:20:40 +00:00

LUMI 2

Lumi2 is the LDAP user management interface, a minimalistic web-ui for managing simple LDAP authentication backends.

It lets an administrator create/read/update/delete users and groups in a user-friendly interface:

Demo

Lumi2 eliminates the need for complex LDAP frontends, such as PhpLdapAdmin, or modifying the DIT (directory information tree) through LDIF files.

It is quite opinionated and suitable only for small-scale deployments (such as a homelab or small enterprise), but it works really well there!

Lumi2 can be used with an existing LDAP deployment, but it makes some core assumptions about the DIT it works on.

Deployment

Lumi2 is designed for use and deployment in a microservice environment.

It works well together with a dockerized LDAP server (such as osixia's OpenLDAP image), and you can use the included docker-compose.yml as a reference. If you are deploying Lumi2 alongside a fresh LDAP instance of the osixia OpenLDAP image, everything works out of the box and you won't ever need to modify your LDAP entries directly.

Assumptions and limitations

Currently, the connection between Lumi2 and your LDAP server does not encrypt traffic using TLS. Your Lumi2 instance and the LDAP server it manages should run on the same host, otherwise it will be possible for a man-in-the-middle to read your user's credentials and personal information.

Lumi2 is simplistic and makes some assumptions about the structure of your LDAP DIT. The DIT hierarchy it expects (or creates) looks like this:

LDAP DIT structure

When deploying a new LDAP server, you don't need to pay too much attention to the rest of this section, as Lumi2 will create the DIT entries for you as necessary. You should, however, be aware of this structure when configuring other applications to use your LDAP backend.

If you point Lumi2 at an existing LDAP server, make sure its DIT matches the structure shown above and described below, otherwise Lumi2 will not work with your LDAP instance.

Users

  • All user entries are direct children of a single OU (organizationalUnit). The cn (name) of this OU is configurable.
  • This OU is a direct child entry of your DIT's root entry
  • The RDN (relative distinguished name) for users is their uid attribute
  • User entries are LDAP objects of type inetOrgPerson. Lumi2 sets (and expects to find) the following attributes for each user:
    • uid - username
    • cn - first name
    • sn - last name
    • displayName - preferred name (or nickname)
    • mail - email address
    • jpegPhoto - profile picture in JPEG format
    • userPassword - SSHA password hash

The uid (username) can contain latin characters, digits, underscores, hypens and periods, and must have a letter as the first character.

Groups

  • All group entries are direct children of a single OU (organizationalUnit). The cn (name) of this OU is configurable.
  • This OU is a direct child entry of your DIT's root entry
  • The RDN (relative distinguished name) for groups is their cn attribute
  • Group entries are LDAP objects of type groupOfUniqueNames:
    • A group always has at least one member (an LDAP limitation)
    • Members of the group are listed in its uniqueMember attribute

The groupname (cn) can contain only latin characters.

Configuration

Your Lumi2 instance is configured using the config.py python file. Customize the file so that Lumi2 can connect to your LDAP server and bind to it with admin credentials.

Make sure you configure a secure SECRET_KEY and ADMIN_PASSWORD prior to deployment, as described below.

It is recommended you use docker-compose for Lumi2 and your LDAP server. Use the docker-compose.yml and config.py files in this repo as a starting point for this.

Security settings

To generate a secret key and a password hash, you first need to import some of Lumi2's dependencies. The easiest way to do this is by using a virtual environment. In the repo's root folder, run the following shell commands:

python -m venv .venv 
source .venv/bin/activate
pip install -r requirements.txt

Lumi2 uses a private key to encrypt session cookies and secrets. Generate a strong secret key now, by running the following command:

python -c 'import secrets; print(secrets.token_hex())'

Replace the insecure SECRET_KEY in your config.py with the random string you just generated.

Next, we need to replace the default password hash in config.py with a more secure one. Pick a strong password, which you will use later to log in to Lumi2, and generate its hash using the following command:

python -c 'from werkzeug.security import generate_password_hash as gen; from getpass import getpass as p; print("Hash: " + gen(p("Password: ")))'

Replace the existing ADMIN_PASSWORD in your config.py with the hash you just generated.

You can deactivate and delete the virtual environment now by running:

deactivate
rm -rf .venv

LDAP settings

Connection settings

Lumi2 needs to know where to reach your LDAP server. This is set in the LDAP_HOSTNAME variable.

What you put here depends on your environment, but if you are using docker-compose with both Lumi2 and OpenLDAP running in the same compose-stack, you can simply set the LDAP container's hostname here.

By default, Lumi2 tries to connnect on 389, the standard LDAP port, but you can specify a non-standard port as well. The following are all valid options:

LDAP_HOSTNAME = 'myhost'

LDAP_HOSTNAME = 'ldap.example.com:9000'

LDAP_HOSTNAME = 'ldap://foo.bar.org'

Important: Communication between Lumi2 and LDAP is currently not encrypted, so anyone listening to the network traffic between the two can read user information being exchanged between Lumi2 and your LDAP server. Deploying Lumi2 alongside LDAP using docker-compose is therefor highly recommended.

Bind user settings

Provide the DN (distinguished name) and password for a user with read- and write-access to your LDAP server by setting the LDAP_BIND_USER_DN and LDAP_BIND_USER_PASSWORD variables respectively.

The LDAP_BASE_DN variable tells Lumi2 what the base DN (the root entry) of your LDAP server is called.

The LDAP_USERS_OU and LDAP_GROUPS_OU variables tell Lumi2 under which OU users and groups can be found or created. Both must be direct children of the root entry. If they do not exist yet on your LDAP server, they will be newly created by Lumi2 when it first starts.

Logging

By default, the lumi2 container logs HTTP access information to the console.

You can additionally write the HTTP access logs to a file by specifying LOG_FILE_PATH. Note that the specified path points to inside the container, so if you want to persist access logs across container restarts, you should set up a Docker volume accordingly. Make sure the specified path is writeable by Lumi2.

LOG_FILE_MAX_SIZE specifies how large the log file can get before being replaced (log rotation). Two access log files are kept: the one currently in use, and the previous one which has reached the maximum size. Any older log files are automatically deleted. To disable log rotation, leave the variable unspecified or set it to 0.

Running the server

The Dockerfile and docker-compose.yml create a Lumi2 instance running behind a waitress WSGI server, exposing the Lumi2 web interface on port 80. It is recommended to use a reverse proxy in front of the waitress server.

Inside the container, Lumi2 and waitress are run by a non-root user. You can specify the UID/GID of this user by setting the LUMI2_UID/LUMI2_GID build arguments (see docker-compose.yml for reference).

You should make sure your config.py file persists across container restarts and is readable by the container's non-root user. The LUMI2_CONFIG environment variable is necessary to tell Lumi2 where config.py can be found inside the container.

Once you have configured docker-compose.yml and config.py, start Lumi2 with

sudo docker-compose up -d --build

Development

Lumi2 is a python application, built using the Flask framework. The following frameworks and/or libraries are also used:

  • Backend:
    • Flask - a python web framework
      • WTForms - a python library for web form handling
      • FlaskWTF - an extension to WTForms specific to Flask
      • flask-restful - a minimalistic library for Flask to build RESTful web APIs
    • ldap3 - python bindings for interaction with LDAP servers
    • pytest - a python testing framework
    • coverage - test code coverage reporting used in conjunction with pytest
    • Pillow - a python image manipulation library
  • Frontend:
    • Bootstrap 5 - a CSS framework and component library
    • Bootstrap Icons - an SVG-icon pack used in conjunction with Bootstrap
    • jQuery - a JavaScript library for DOM manipulation and AJAX routines

Theming (SASS)

To customize the bootstrap theme, some of Bootstrap 5's SASS variables are modified in /scss/bootstrap.scss and then compiled using a SASS preprocessor.

Using a CLI SASS preprocessor:

npm install -g sass

You can modify the SASS variables and then compile into CSS:

sass scss/bootstrap.scss lumi2/static/css/bootstrap.css

Testing

Make sure all dependencies listed in requirements.txt are installed. To run all unit tests, simply run the following from within the repository root:

coverage run -m pytest && coverage report

This will run all unit tests and display the test coverage in your terminal.