Ecosyste.ms: Awesome

An open API service indexing awesome lists of open source software.

Awesome Lists | Featured Topics | Projects

https://github.com/mgitrov/ldap-setup


https://github.com/mgitrov/ldap-setup

Last synced: 20 days ago
JSON representation

Awesome Lists containing this project

README

        

# Introduction
This repository demonstrates a setup for user authentication using LDAP combined with MFA in a Flask web application. The solution involves setting up an LDAP server with Docker, creating and managing users, and integrating an additional security layer using Time-based One-Time Passwords (TOTP) for MFA.

# Prerequisites
* Git
* Docker

# Directory Structure
```
LDAP-setup/
├── Dockerfile
├── docker-compose.yml
├── init.ldif
├── .env
├── README.md
└── app/
├── templates/
│ ├── login.html
│ ├── setup_mfa.html
│ ├── mfa.html
│ └── welcome.html
└── app.py
```

# Key Components
## Docker Compose
Defines the services, environment variables, and volumes for setting up the LDAP server and the Flask web application.
``` yaml
version: '3.8'

services:
ldap:
image: osixia/openldap:latest
container_name: ldap-server
environment:
LDAP_ORGANISATION: ${LDAP_ORGANISATION} # Sets the organization name for the LDAP directory.
LDAP_DOMAIN: ${LDAP_DOMAIN} # Sets the domain components (DC) for the LDAP directory.
LDAP_ADMIN_PASSWORD: ${LDAP_ADMIN_PASSWORD} # Sets the password for the LDAP admin user.
ports:
- "389:389" # For LDAP.
volumes:
- ldap_data:/var/lib/ldap
- ldap_config:/etc/ldap/slapd.d
- ./init.ldif:/container/service/slapd/assets/config/bootstrap/ldif/50-bootstrap.ldif # Bind mount for initial LDIF file.
command: --copy-service # Ensures that the "init.ldif" file is copied into the appropriate directory before the LDAP server starts.

ldap-auth:
build: .
container_name: ldap-auth
ports:
- "5000:5000"
depends_on:
- ldap

volumes:
ldap_data:
ldap_config:
```
## LDAP Configuration
Initializes the LDAP directory with an organizational unit and users.
``` ldif
dn: ou=Users,dc=my-domain,dc=com
objectClass: organizationalUnit
ou: Users

dn: uid=maxim.petrov,ou=Users,dc=my-domain,dc=com
objectClass: inetOrgPerson
cn: Maxim Petrov
sn: Petrov
givenName: Maxim
mail: [email protected]
uid: maxim.petrov
userPassword: mypassword

dn: uid=israel.israeli,ou=Users,dc=my-domain,dc=com
objectClass: inetOrgPerson
cn: Israel Israeli
sn: Israeli
givenName: Israel
mail: [email protected]
uid: israel.israeli
userPassword: password123
```
## Flask Web Application
The main application file for the Flask web application, handling routes and authentication logic.
``` python
from flask import Flask, request, render_template, redirect, url_for, flash, session
from ldap3 import Server, Connection, SIMPLE
import pyotp # Library used for generating and verifying TOTP tokens for MFA.
import qrcode # Library used for generating QR codes.

# io and base64 are used for handling image data for the QR code.
import io
import base64

app = Flask(__name__) # Initiates a Flask application instance.
app.secret_key = "dummy_secret_key" # A random string used by Flask to secure things like sessions, cookies, etc.

LDAP_SERVER = "ldap://ldap-server" # The URL of the LDAP server.
# As the LDAP server is running on a container, I need to specify the name of the container as part of the URL.

BASE_DN = "dc=my-domain,dc=com" # The base distinguished name (DN) for the LDAP directory. Represents the root of the LDAP tree.
# In other words, specifies the starting point in the LDAP directory tree for searches.

# What DN is used to login to the LDAP directory to perform searches and other operations.
BIND_DN = "cn=admin,dc=my-domain,dc=com"
BIND_PASSWORD = "admin_password"

user_secrets = {} # Stores each user's unique generated secret, keyed by the username.

@app.route("/")
def index():
if "authenticated" in session:
return redirect(url_for("welcome"))
return redirect(url_for("login"))

@app.route("/login", methods=["GET", "POST"])
def login():
if request.method == "POST":
username = request.form["username"]
password = request.form["password"]
user_dn = f"uid={username},ou=Users,{BASE_DN}" # Constructs user's DN based on the provided username and the "BASE_DN".

try:
server = Server(LDAP_SERVER)
connection = Connection(server, user=user_dn, password=password, authentication=SIMPLE) # Connects to the LDAP server using
# a plaintext username and password for authentication.

if connection.bind():
session["username"] = username # Stores the username in the current session.

if username not in user_secrets:
secret = pyotp.random_base32() # Generates a new secret (a random string of characters encoded in base32) for the current user.
user_secrets[username] = secret # Assigns the generated secret to the current user.
session["setup_mfa"] = True
return redirect(url_for("setup_mfa")) # Redirects the user the to MFA setup.

return redirect(url_for("mfa")) # Redirects to MFA verification if the user already has a secret generated for him.

else:
flash("Invalid credentials", "danger")
return redirect(url_for("login"))

except Exception as error:
flash(f"Error: {str(error)}", "danger")
return redirect(url_for("login"))

return render_template("login.html") # Handles the GET request by rendering the login form.

@app.route("/setup_mfa")
def setup_mfa():
if "username" not in session or "setup_mfa" not in session: # Checks if the user is logged in.
return redirect(url_for("login"))

username = session["username"]
secret = user_secrets[username]
otp_uri = pyotp.totp.TOTP(secret).provisioning_uri(name=username, issuer_name="ldap-setup") # Creates the TOTP URI out of the user's
# unique generated secret for the authenticator app to generate the TOTP token.

img = qrcode.make(otp_uri) # Creates the QR code out of the TOTP URI created above.

# Creates a buffer in memory to store the QR code image (instead of a file on disk).
buf = io.BytesIO()
img.save(buf, format="PNG")

img_str = base64.b64encode(buf.getvalue()).decode("ascii") # Converts the QR code image to a Base64 string to embed it in the HTML.

return render_template("setup_mfa.html", qr_code=img_str)

@app.route("/mfa", methods=["GET", "POST"])
def mfa():
if "username" not in session: # Checks if the user is logged in.
return redirect(url_for("login"))

if request.method == "POST":
token = request.form["token"] # User's input token will be processed.
username = session["username"]

secret = user_secrets[username]
totp = pyotp.TOTP(secret) # Generates the TOTP token for comparison with user's input token.
if totp.verify(token):
session.pop("setup_mfa", None) # Removes the "setup_mfa" flag.
session["authenticated"] = True # Sets the "authenticated" flag to true.
flash("Login successful", "success")
return redirect(url_for("welcome"))

else:
flash("Invalid MFA token", "danger")

return render_template("mfa.html")

@app.route("/welcome")
def welcome():
if "authenticated" not in session:
return redirect(url_for("login"))

return render_template("welcome.html")

@app.route("/logout", methods=["POST"])
def logout():
session.pop("username", None)
session.pop("authenticated", None)
return redirect(url_for("login"))

if __name__ == "__main__":
app.run(host="0.0.0.0", port=5000) # Allows the Flask server to be accessed from any IP address.
```
### Routes
| Route | Description |
| ------------ | ------------------------------------------------------------------- |
| `/` | Displays the login page and handles user login. |
| `/setup_mfa` | Displays the QR code for the user to scan and set up TOTP-based MFA.|
| `/mfa` | Displays the page for entering the TOTP token and verifies the token.|
| `/welcome` | Displays a welcome message after the user is authenticated. |
| `/logout` | Logs the user out and clears the session. |

# Deployment
1. Open the Terminal and run the following command:
``` bash
git clone https://github.com/MGitrov/LDAP-setup.git
```
2. Then you will have to go inside the "LDAP-setup" folder using the following command:
```bash
cd LDAP-setup
```
3. To start services, you will have to enter the following command:
```bash
docker-compose up -d
```
4. You may access the web application via your browser by typing the following address:
```bash
http://localhost:5000
```
5. Log in using the LDAP credentials specified in the ```init.ldif``` file:
* Username: maxim.petrov
* Password: mypassword

or
* Username: israel.israeli
* Password: password123
6. Upon first login, scan the QR code with an authentication application (e.g., Google Authenticator, Authy, Microsoft Authenticator).

7. Enter the TOTP token (the 6 digit number) generated by the application.