222 lines
10 KiB
Markdown
222 lines
10 KiB
Markdown
# 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](/docs/demo.gif)
|
|
|
|
Lumi2 eliminates the need for complex LDAP frontends, such as [PhpLdapAdmin](https://phpldapadmin.sourceforge.net/wiki/index.php/Main_Page),
|
|
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](#assumptions-and-limitations) 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](https://github.com/osixia/docker-openldap)),
|
|
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](/docs/ldap-dit-structure.svg)
|
|
|
|
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` - SHA512 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:
|
|
|
|
```bash
|
|
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:
|
|
|
|
```bash
|
|
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:
|
|
|
|
```bash
|
|
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:
|
|
```bash
|
|
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:
|
|
|
|
```python
|
|
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](https://docs.pylonsproject.org/projects/waitress/en/latest/) 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](https://flask.palletsprojects.com/en/2.2.x/) - a python web framework
|
|
- [WTForms](https://wtforms.readthedocs.io/en/3.0.x/) - a python library for web form handling
|
|
- [FlaskWTF](https://flask-wtf.readthedocs.io/en/1.0.x/) - an extension to WTForms specific to Flask
|
|
- [flask-restful](https://flask-restful.readthedocs.io/en/latest/quickstart.html) - a minimalistic library for Flask to build RESTful web APIs
|
|
- [ldap3](https://ldap3.readthedocs.io/en/latest/) - python bindings for interaction with LDAP servers
|
|
- [pytest](https://docs.pytest.org/en/7.2.x/) - a python testing framework
|
|
- [coverage](https://coverage.readthedocs.io/en/6.5.0/) - test code coverage reporting used in conjunction with pytest
|
|
- [Pillow](https://pillow.readthedocs.io/en/stable/) - a python image manipulation library
|
|
- Frontend:
|
|
- [Bootstrap 5](https://getbootstrap.com/docs/5.2/getting-started/introduction/) - a CSS framework and component library
|
|
- [Bootstrap Icons](https://icons.getbootstrap.com/) - an SVG-icon pack used in conjunction with Bootstrap
|
|
- [jQuery](https://api.jquery.com/) - 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:
|
|
|
|
```bash
|
|
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:
|
|
|
|
```bash
|
|
coverage run -m pytest && coverage report
|
|
```
|
|
|
|
This will run all unit tests and display the test coverage in your terminal.
|