lldap/README.md
Valentin Tolmer 3eb53ba5bf model: rename to auth
Since the "model" doesn't contain any message from the API anymore, and
instead contains only the structures needed for authentication, it was
renamed as such.
2021-08-31 20:32:55 +02:00

157 lines
5.8 KiB
Markdown

# lldap - Light LDAP implementation for authentication
WARNING: This project is still WIP, it's still missing core functionality. For
updates, follow [@nitnelave1](https://twitter.com/nitnelave1).
This project is an lightweight authentication server that provides an
opinionated, simplified LDAP interface for authentication: clients that can
only speak LDAP protocol can talk to it and use it as an authentication server.
The goal is _not_ to provide a full LDAP server; if you're interested in that,
check out OpenLDAP. This server is made to be:
* simple to setup (no messing around with `slapd`)
* simple to manage (friendly web UI)
* opinionated with basic defaults so you don't have to understand the
subtleties of LDAP.
It mostly targets self-hosting servers, with open-source components like
Nextcloud, Airsonic and so on that only support LDAP as a source of external
authentication.
## Architecture
The server is entirely written in Rust, using [actix](https://actix.rs) 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.
* Listens on another port for HTTP traffic.
* The authentication API, based on JWTs, is under "/auth".
* The user management API is under "/api" (POST requests only).
* 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/`: The backend.
* `domain/`: Domain-specific logic: users, groups, checking passwords...
* `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).
TODO: Add client-side password hashing.
### 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 are welcome! Just fork and open a PR. Or just file a bug.
We don't have a code of conduct, just be respectful and remember that it's just
normal people doing this for free on their free time.
Make sure that you run `cargo fmt` in each crate that you modified (top-level,
`app/` and `auth/`) before creating the PR.
### Setup
#### With Docker
The image is available at `nitnelave/lldap`. You should persist the `/data`
folder, which contains your configuration, the database and the private key
file (unless you move them in the config).
Configure the server by copying the `lldap_config.docker_template.toml` to
`/data/lldap_config.toml` and updating the configuration values (especially the
`jwt_secret` and `ldap_user_pass`, unless you override them with env variables).
Example for docker compose:
```yaml
volumes:
lldap_data:
driver: local
services:
lldap:
image: nitnelave/lldap
ports:
# For LDAP
- "3890:3890"
# For the web front-end
- "17170:17170"
volumes:
- "lldap_data:/data"
environment:
- JWT_SECRET=REPLACE_WITH_RANDOM
- LDAP_USER_PASS=REPLACE_WITH_PASSWORD
- LDAP_BASE_DN=dc=example,dc=com
```
Then the service will listen on two ports, one for LDAP and one for the web
front-end.
#### From source
To bring up the server, you'll need to compile the frontend. In addition to
cargo, you'll need:
* WASM-pack: `cargo install wasm-pack`
* rollup.js: `npm install rollup`
Then you can build the frontend files with `./app/build.sh` (you'll need to run
this after every front-end change to update the WASM package served).
To bring up the server, just run `cargo run`. The default config is in
`src/infra/configuration.rs`, but you can override it by creating an
`lldap_config.toml`, setting environment variables or passing arguments to
`cargo run`.