Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
# Another LDAP

Another LDAP is a form-based authentication for Active Directory / LDAP server.

Another LDAP provides Authentication and Authorization for your applications running on Kubernetes.
Expand All @@ -14,6 +15,7 @@ Another LDAP provides Authentication and Authorization for your applications run
![Alt text](another-ldap.png?raw=true "Another LDAP")

## Features

- Authentication and Authorization for applications.
- Authorization via LDAP groups, supports regex in groups list.
- Supports protocols `ldap://` and `ldaps://`.
Expand All @@ -24,6 +26,7 @@ Another LDAP provides Authentication and Authorization for your applications run
- Log format in Plain-Text or JSON.

## Installation

- Clone this repository or download the manifests from the directory `kubernetes`.
- Edit the ingress, config-map and secrets with your configuration.
- ALDAP is installed in the namespace `another`.
Expand All @@ -37,7 +40,9 @@ kubectl apply -f .
## Configuration

### Example 1: Authentication

The following example provides authentication for the application `my-app`.

- The authentication validates username and password.

```
Expand All @@ -54,6 +59,9 @@ metadata:
location @login {
return 302 https://another-ldap.testmyldap.com/?protocol=$pass_access_scheme&callback=$host;
}
location /logout {
return 302 https://another-ldap.testmyldap.com/logout?protocol=$pass_access_scheme&callback=$host;
}
spec:
rules:
- host: my-app.testmyldap.com
Expand All @@ -69,7 +77,9 @@ spec:
```

### Example 2: Authentication and Authorization

The following example provides authentication and authorization for the application `my-app`.

- The authentication validates username and password.
- The authorization validates if the user has the LDAP group `DevOps production environment`.

Expand All @@ -89,6 +99,9 @@ metadata:
location @login {
return 302 https://another-ldap.testmyldap.com/?protocol=$pass_access_scheme&callback=$host;
}
location /logout {
return 302 https://another-ldap.testmyldap.com/logout?protocol=$pass_access_scheme&callback=$host;
}
spec:
rules:
- host: my-app.testmyldap.com
Expand All @@ -104,7 +117,9 @@ spec:
```

### Example 3: Authentication, Authorization and response headers

The following example provides authentication and authorization for the application `my-app` and calls the application with the headers `x-username` and `x-groups`.

- The authentication validates username and password.
- The authorization validates if the user has one of the following LDAP groups `DevOps production environment` or `DevOps QA environment`.
- Nginx will return the header `x-username` to the application that contains the username authenticated.
Expand All @@ -129,6 +144,9 @@ metadata:
location @login {
return 302 https://another-ldap.testmyldap.com/?protocol=$pass_access_scheme&callback=$host;
}
location /logout {
return 302 https://another-ldap.testmyldap.com/logout?protocol=$pass_access_scheme&callback=$host;
}
spec:
rules:
- host: my-app.testmyldap.com
Expand All @@ -144,6 +162,7 @@ spec:
```

## Available parameters

All parameters are defined in the config-map and secret manifests.

All values type are `string`.
Expand All @@ -155,12 +174,15 @@ The parameter `LDAP_BIND_DN` supports variable expansion with the username, you
The parameter `COOKIE_DOMAIN` define the scope of the cookie, for example if you need to authentication/authorizate the domain `testmyldap.com` you should set the wildcard `.testmyldap.com` (notice the dot at the beginning).

## Supported HTTP request headers

The variables send via HTTP headers take precedence over environment variables.

- `Ldap-Allowed-Users`
- `Ldap-Allowed-Groups`
- `Ldap-Conditional-Groups`: Default=`"or"`
- `Ldap-Conditional-Users-Groups`: Default=`"or"`

## HTTP response headers

- `x-username` Contains the authenticated username
- `x-groups` Contains the user's matches groups
9 changes: 8 additions & 1 deletion files/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,13 @@ def logout():
session.clear()
except KeyError:
pass
return redirect(url_for('index'))
# Get return page to redirect the user after successful logout
protocol = request.args.get('protocol', default='', type=str)
callback = request.args.get('callback', default='', type=str)
if (protocol or callback):
return redirect(url_for('index', protocol=protocol, callback=callback, alert=False))
else:
return redirect(url_for('index'))

@app.route('/', methods=['GET'])
def index():
Expand All @@ -132,6 +138,7 @@ def index():
'metadata': {
'title': param.get('METADATA_TITLE', 'Another LDAP', str),
'description': param.get('METADATA_DESCRIPTION', '', str),
'login_image': param.get('METADATA_LOGIN_IMAGE', None, str),
'footer': param.get('METADATA_FOOTER', 'Powered by Another LDAP', str)
},
'authenticated': False,
Expand Down
14 changes: 12 additions & 2 deletions files/templates/base.html
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
<!doctype html>
<html data-theme="dark">

<head>
<meta charset="utf-8">
<meta name="robots" content="noindex, nofollow">
Expand All @@ -9,12 +10,21 @@
<meta name="author" content="Another LDAP">
<link rel="stylesheet" href="{{ url_for('static', filename='pico.css') }}">
<link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
<link href="data:image/x-icon;base64,AAABAAEAEBAQAAEABAAoAQAAFgAAACgAAAAQAAAAIAAAAAEABAAAAAAAgAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD//wAA//8AAP//AADwDwAA8A8AAPAPAADwDwAA8A8AAPAPAADwDwAA+98AAPvfAAD73wAA/D8AAP//AAD//wAA" rel="icon" type="image/x-icon" />
<link
href="data:image/x-icon;base64,AAABAAEAEBAQAAEABAAoAQAAFgAAACgAAAAQAAAAIAAAAAEABAAAAAAAgAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD//wAA//8AAP//AADwDwAA8A8AAPAPAADwDwAA8A8AAPAPAADwDwAA+98AAPvfAAD73wAA/D8AAP//AAD//wAA"
rel="icon" type="image/x-icon" />
</head>

<body>
{% block content %}{% endblock %}
<footer class="container-fluid">
<small>{{ layout.metadata.footer }} • Picture by <a href="https://unsplash.com/photos/5cwigXmGWTo" class="secondary">Ivan Bandura</a></small>
{% if layout.metadata.login_image %}
<small>{{ layout.metadata.footer }}</small>
{% else %}
<small>{{ layout.metadata.footer }} • Picture by <a href="https://unsplash.com/photos/5cwigXmGWTo"
class="secondary">Ivan Bandura</a></small>
{% endif %}
</footer>
</body>

</html>
61 changes: 34 additions & 27 deletions files/templates/login.html
Original file line number Diff line number Diff line change
@@ -1,32 +1,39 @@
{% extends "base.html" %}

{% block content %}
<main class="container">
{% if layout.alert %}<div class="alert">{{ layout.alert }}</div>{% endif %}
<article class="grid">
<div>
{% if layout.authenticated %}
<hgroup>
<h1>Welcome {{ layout.username }}</h1>
<h2>{{ layout.metadata.title }}</h2>
</hgroup>
<form action="{{ url_for('logout') }}" method="POST">
<button type="submit" class="contrast">Logout</button>
</form>
{% else %}
<hgroup>
<h1>Sign in</h1>
<h2>{{ layout.metadata.title }}</h2>
</hgroup>
<form action="{{ url_for('login', protocol=layout.protocol, callback=layout.callback) }}" method="POST">
<input type="text" name="username" placeholder="Username" aria-label="Username" autocomplete="off" required>
<input type="password" name="password" placeholder="Password" aria-label="Password" autocomplete="off" required>
<button type="submit" class="contrast">Login</button>
</form>
{% endif %}
</div>
<div style="background-image: url('{{ url_for('static', filename='photo.jpg') }}')"></div>
</article>
<main class="container">
{% if layout.alert %}<div class="alert">{{ layout.alert }}</div>{% endif %}
<article class="grid">
<div>
{% if layout.authenticated %}
<hgroup>
<h1>Welcome {{ layout.username }}</h1>
<h2>{{ layout.metadata.title }}</h2>
</hgroup>
<form action="{{ url_for('logout') }}" method="POST">
<button type="submit" class="contrast">Logout</button>
</form>
{% else %}
<hgroup>
<h1>Sign in</h1>
<h2>{{ layout.metadata.title }}</h2>
</hgroup>
<form action="{{ url_for('login', protocol=layout.protocol, callback=layout.callback) }}" method="POST">
<input type="text" name="username" placeholder="Username" aria-label="Username" autocomplete="off"
required>
<input type="password" name="password" placeholder="Password" aria-label="Password" autocomplete="off"
required>
<button type="submit" class="contrast">Login</button>
</form>
{% endif %}
</div>
{% if layout.metadata.login_image %}
<div style="background-image: url('{{ layout.metadata.login_image }}')"></div>
{% else %}
<div style="background-image: url('{{ url_for('static', filename='photo.jpg') }}')"></div>
{% endif %}

</main>
</article>

</main>
{% endblock %}
3 changes: 2 additions & 1 deletion kubernetes/config-map.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -18,5 +18,6 @@ data:
COOKIE_DOMAIN: ""
METADATA_TITLE: "Another LDAP"
METADATA_DESCRIPTION: ""
METADATA_LOGIN_IMAGE: ""
METADATA_FOOTER: "Powered by Another LDAP"
PERMANENT_SESSION_LIFETIME: "7"
PERMANENT_SESSION_LIFETIME: "7"