readme: Improve title, add comparisons

This commit is contained in:
Valentin Tolmer 2021-11-20 17:37:29 +01:00 committed by nitnelave
parent 6456149e50
commit 35d0cc0fb0
3 changed files with 187 additions and 103 deletions

View File

@ -22,3 +22,4 @@ server_key
users.db* users.db*
screenshot.png screenshot.png
recipe.json recipe.json
*.md

205
README.md
View File

@ -1,25 +1,51 @@
# lldap - Light LDAP implementation for authentication <h1 align="center">lldap - Light LDAP implementation for authentication</h1>
![Build](https://github.com/nitnelave/lldap/actions/workflows/rust.yml/badge.svg) <p align="center">
![Discord](https://img.shields.io/discord/898492935446876200) <i style="font-size:24px">LDAP made easy.</i>
![Twitter Follow](https://img.shields.io/twitter/follow/nitnelave1?style=social) </p>
WARNING: This project is still in alpha, with the basic core functionality <p align="center">
implemented but still very rough. For updates, follow <a href="https://github.com/nitnelave/lldap/actions/workflows/rust.yml?query=branch%3Amain">
[@nitnelave1](https://twitter.com/nitnelave1) or join our [Discord <img
server](https://discord.gg/h5PEdRMNyP)! src="https://github.com/nitnelave/lldap/actions/workflows/rust.yml/badge.svg"
alt="Build"/>
</a>
<a href="https://discord.gg/h5PEdRMNyP">
<img alt="Discord" src="https://img.shields.io/discord/898492935446876200?label=discord&logo=discord" />
</a>
<a href="https://twitter.com/nitnelave1?ref_src=twsrc%5Etfw">
<img
src="https://img.shields.io/twitter/follow/nitnelave1?style=social"
alt="Twitter Follow"/>
</a>
<a href="https://github.com/rust-secure-code/safety-dance/">
<img
src="https://img.shields.io/badge/unsafe-forbidden-success.svg"
alt="Unsafe forbidden"/>
</a>
<a href="https://app.codecov.io/gh/nitnelave/lldap">
<img alt="Codecov" src="https://img.shields.io/codecov/c/github/nitnelave/lldap" />
</a>
</p>
## About
This project is an lightweight authentication server that provides an This project is a lightweight authentication server that provides an
opinionated, simplified LDAP interface for authentication: clients that can opinionated, simplified LDAP interface for authentication. It integrates with
only speak LDAP protocol can talk to it and use it as an authentication server. many backends, from KeyCloak to Authelia to Nextcloud and more!
![Screenshot of the user list page](screenshot.png) <img
src="https://raw.githubusercontent.com/nitnelave/lldap/master/screenshot.png"
alt="Screenshot of the user list page"
width="50%"
align="right"
/>
The goal is _not_ to provide a full LDAP server; if you're interested in that, The goal is _not_ to provide a full LDAP server; if you're interested in that,
check out OpenLDAP. This server is a user management system that is: check out OpenLDAP. This server is a user management system that is:
* simple to setup (no messing around with `slapd`) * simple to setup (no messing around with `slapd`),
* simple to manage (friendly web UI) * simple to manage (friendly web UI),
* low resources,
* opinionated with basic defaults so you don't have to understand the * opinionated with basic defaults so you don't have to understand the
subtleties of LDAP. subtleties of LDAP.
@ -31,7 +57,7 @@ For more features (OAuth/OpenID support, reverse proxy, ...) you can install
other components (KeyCloak, Authelia, ...) using this server as the source of other components (KeyCloak, Authelia, ...) using this server as the source of
truth for users, via LDAP. truth for users, via LDAP.
## Setup ## Installation
### With Docker ### With Docker
@ -87,6 +113,31 @@ To bring up the server, just run `cargo run`. The default config is in
`lldap_config.toml`, setting environment variables or passing arguments to `lldap_config.toml`, setting environment variables or passing arguments to
`cargo run`. `cargo run`.
### Cross-compilation
No Docker image is provided for other architectures, due to the difficulty of
setting up cross-compilation inside a Docker image.
Some pre-compiled binaries are provided for each release, starting with 0.2.
If you want to cross-compile, you can do so by installing
[`cross`](https://github.com/rust-embedded/cross):
```sh
cargo install cross
cross build --target=armv7-unknown-linux-musleabihf -p lldap --release
./app/build.sh
```
(Replace `armv7-unknown-linux-musleabihf` with the correct Rust target for your
device.)
You can then get the compiled server binary in
`target/armv7-unknown-linux-musleabihf/release/lldap` and the various needed files
(`index.html`, `main.js`, `pkg` folder) in the `app` folder. Copy them to the
Raspberry Pi (or other target), with the folder structure maintained (`app`
files in an `app` folder next to the binary).
## Client configuration ## Client configuration
To configure the services that will talk to LLDAP, here are the values: To configure the services that will talk to LLDAP, here are the values:
@ -108,10 +159,42 @@ admin rights in the Web UI.
### Sample client configurations ### Sample client configurations
Some specific clients have been tested to work and come with sample Some specific clients have been tested to work and come with sample
configuration files, or guides. See the `example_configs` folder for help with: configuration files, or guides. See the [`example_configs`](example_configs)
- Authelia folder for help with:
- KeyCloak - [Authelia](example_configs/authelia_config.yml)
- Jisti Meet - [KeyCloak](example_configs/keycloak.md)
- [Jisti Meet](example_configs/jitsi_meet.conf)
## Comparisons with other services
### vs OpenLDAP
OpenLDAP is a monster of a service that implements all of LDAP and all of its
extensions, plus some of its own. That said, if you need all that flexibility,
it might be what you need! Note that installation can be a bit painful
(figuring out how to use `slapd`) and people have mixed experiences following
tutorials online. If you don't configure it properly, you might end up storing
passwords in clear, so a breach of your server would reveal all the stored
passwords!
OpenLDAP doesn't come with a UI: if you want a web interface, you'll have to
install one (not that many that look nice) and configure it.
LLDAP is much simpler to setup, has a much smaller image (10x smaller, 20x if
you add PhpLdapAdmin), and comes packed with its own purpose-built wed UI.
### vs FreeIPA
FreeIPA is the one-stop shop for identity management: LDAP, Kerberos, NTP, DNS, Samba, you name it, it has it. In addition to user
management, it also does security policies, single sign-on, certificate
management, linux account management and so on.
If you need all of that, go for it! Keep in mind that a more complex system is
more complex to maintain, though.
LLDAP is much lighter to run (<100 MB RAM including the DB), easier to
configure (no messing around with DNS or security policies) and simpler to
use. It also comes conveniently packed in a docker container.
## I can't log in! ## I can't log in!
@ -132,90 +215,6 @@ set isn't working, try the following:
- Make sure you restart the server. - Make sure you restart the server.
- If it's still not working, join the [Discord server](https://discord.gg/h5PEdRMNyP) to ask for help. - If it's still not working, join the [Discord server](https://discord.gg/h5PEdRMNyP) to ask for help.
## Architecture
The server is entirely written in Rust, using [actix](https://actix.rs) for the
backend and [yew](https://yew.rs) for the frontend.
Backend:
* Listens on a port for LDAP protocol.
* Only a small, read-only subset of the LDAP protocol is supported.
* An extension to allow resetting the password through LDAP will be added.
* Listens on another port for HTTP traffic.
* The authentication API, based on JWTs, is under "/auth".
* The user management API is a GraphQL API under "/api/graphql". The schema
is defined in `schema.graphql`.
* The static frontend files are served by this port too.
Note that secure protocols (LDAPS, HTTPS) are currently not supported. This can
be worked around by using a reverse proxy in front of the server (for the HTTP
API) that wraps/unwraps the HTTPS messages, or only open the service to
localhost or other trusted docker containers (for the LDAP API).
Frontend:
* User management UI.
* Written in Rust compiled to WASM as an SPA with the Yew library.
* Based on components, with a React-like organization.
Data storage:
* The data (users, groups, memberships, active JWTs, ...) is stored in SQL.
* Currently only SQLite is supported (see
https://github.com/launchbadge/sqlx/issues/1225 for what blocks us from
supporting more SQL backends).
### Code organization
* `auth/`: Contains the shared structures needed for authentication, the
interface between front and back-end. In particular, it contains the OPAQUE
structures and the JWT format.
* `app/`: The frontend.
* `src/components`: The elements containing the business and display logic of
the various pages and their components.
* `src/infra`: Various tools and utilities.
* `server/`: The backend.
* `src/domain/`: Domain-specific logic: users, groups, checking passwords...
* `src/infra/`: API, both GraphQL and LDAP
## Authentication
### Passwords
Passwords are hashed using Argon2, the state of the art in terms of password
storage. They are hashed using a secret provided in the configuration (which
can be given as environment variable or command line argument as well): this
should be kept secret and shouldn't change (it would invalidate all passwords).
Authentication is done via the OPAQUE protocol, meaning that the passwords are
never sent to the server, but instead the client proves that they know the
correct password (zero-knowledge proof). This is likely overkill, especially
considered that the LDAP interface requires sending the password to the server,
but it's one less potential flaw (especially since the LDAP interface can be
restricted to an internal docker-only network while the web app is exposed to
the Internet).
### JWTs and refresh tokens
When logging in for the first time, users are provided with a refresh token
that gets stored in an HTTP-only cookie, valid for 30 days. They can use this
token to get a JWT to get access to various servers: the JWT lists the groups
the user belongs to. To simplify the setup, there is a single JWT secret that
should be shared between the authentication server and the application servers;
and users don't get a different token per application server
(this could be implemented, we just didn't have any use case yet).
JWTs are only valid for one day: when they expire, a new JWT can be obtained
from the authentication server using the refresh token. If the user stays
logged in, they would only have to type their password once a month.
#### Logout
In order to handle logout correctly, we rely on a blacklist of JWTs. When a
user logs out, their refresh token is removed from the backend, and all of
their currently valid JWTs are added to a blacklist. Incoming requests are
checked against this blacklist (in-memory, faster than calling the database).
Applications that want to use these JWTs should subscribe to be notified of
blacklisted JWTs (TODO: implement the PubSub service and API).
## Contributions ## Contributions
Contributions are welcome! Just fork and open a PR. Or just file a bug. Contributions are welcome! Just fork and open a PR. Or just file a bug.

84
architecture.md Normal file
View File

@ -0,0 +1,84 @@
# Architecture
The server is entirely written in Rust, using [actix](https://actix.rs) for the
backend and [yew](https://yew.rs) for the frontend.
Backend:
* Listens on a port for LDAP protocol.
* Only a small, read-only subset of the LDAP protocol is supported.
* An extension to allow resetting the password through LDAP will be added.
* Listens on another port for HTTP traffic.
* The authentication API, based on JWTs, is under "/auth".
* The user management API is a GraphQL API under "/api/graphql". The schema
is defined in `schema.graphql`.
* The static frontend files are served by this port too.
Note that secure protocols (LDAPS, HTTPS) are currently not supported. This can
be worked around by using a reverse proxy in front of the server (for the HTTP
API) that wraps/unwraps the HTTPS messages, or only open the service to
localhost or other trusted docker containers (for the LDAP API).
Frontend:
* User management UI.
* Written in Rust compiled to WASM as an SPA with the Yew library.
* Based on components, with a React-like organization.
Data storage:
* The data (users, groups, memberships, active JWTs, ...) is stored in SQL.
* Currently only SQLite is supported (see
https://github.com/launchbadge/sqlx/issues/1225 for what blocks us from
supporting more SQL backends).
### Code organization
* `auth/`: Contains the shared structures needed for authentication, the
interface between front and back-end. In particular, it contains the OPAQUE
structures and the JWT format.
* `app/`: The frontend.
* `src/components`: The elements containing the business and display logic of
the various pages and their components.
* `src/infra`: Various tools and utilities.
* `server/`: The backend.
* `src/domain/`: Domain-specific logic: users, groups, checking passwords...
* `src/infra/`: API, both GraphQL and LDAP
## Authentication
### Passwords
Passwords are hashed using Argon2, the state of the art in terms of password
storage. They are hashed using a secret provided in the configuration (which
can be given as environment variable or command line argument as well): this
should be kept secret and shouldn't change (it would invalidate all passwords).
Authentication is done via the OPAQUE protocol, meaning that the passwords are
never sent to the server, but instead the client proves that they know the
correct password (zero-knowledge proof). This is likely overkill, especially
considered that the LDAP interface requires sending the password to the server,
but it's one less potential flaw (especially since the LDAP interface can be
restricted to an internal docker-only network while the web app is exposed to
the Internet).
### JWTs and refresh tokens
When logging in for the first time, users are provided with a refresh token
that gets stored in an HTTP-only cookie, valid for 30 days. They can use this
token to get a JWT to get access to various servers: the JWT lists the groups
the user belongs to. To simplify the setup, there is a single JWT secret that
should be shared between the authentication server and the application servers;
and users don't get a different token per application server
(this could be implemented, we just didn't have any use case yet).
JWTs are only valid for one day: when they expire, a new JWT can be obtained
from the authentication server using the refresh token. If the user stays
logged in, they would only have to type their password once a month.
#### Logout
In order to handle logout correctly, we rely on a blacklist of JWTs. When a
user logs out, their refresh token is removed from the backend, and all of
their currently valid JWTs are added to a blacklist. Incoming requests are
checked against this blacklist (in-memory, faster than calling the database).
Applications that want to use these JWTs should subscribe to be notified of
blacklisted JWTs (TODO: implement the PubSub service and API).