Code Monkey home page Code Monkey logo

another-ldap-auth's Introduction

โš ๏ธ New version of Another LDAP via form-based here https://github.com/dignajar/another-ldap

Another LDAP authentication

LDAP Authentication for Nginx, Nginx ingress controller (Kubernetes), HAProxy (haproxy-auth-request) or any webserver/reverse proxy with authorization based on the result of a subrequest.

Another LDAP Authentication is an implementation of the ldap-auth-daemon services described in the official blog from Nginx in the following article.

Another LDAP Authentication it's prepared to run inside a Docker container, also you can run the Python script without the Docker container.

Docker Hub Kubernetes YAML manifests codebeat badge release license

Features

  • Supports ldap and ldaps.
  • Provide a cache for users and groups, you can set the cache expiration in minutes.
  • Supports validation by groups, regex in groups are supported.
  • Supports TLS via self-signed certificate.
  • Supports configuration via headers or via environment variables.
  • Supports HTTP response headers such as username and matched groups.
  • Brute force protection.
  • Log format in Plain-Text or JSON.

Diagram

Another LDAP Authentication

Available configurations parameters

The parameters can be sent via environment variables or via HTTP headers, also you can combine them.

The parameter LDAP_SEARCH_FILTER support variable expansion with the username, you can do something like this (sAMAccountName={username}) and {username} is going to be replaced by the username typed in the login form.

The parameter LDAP_BIND_DN support variable expansion with the username, you can do something like this {username}@TESTMYLDAP.com or UID={username},OU=PEOPLE,DC=TESTMYLDAP,DC=COM and {username} is going to be replaced by the username typed in the login form.

All values type are string.

Environment variables

Key Default Values Description Example
LDAP_ENDPOINT LDAP URL with the protocol and the port number. ldaps://testmyldap.com:636
LDAP_MANAGER_DN_USERNAME Username to bind and search in the LDAP tree. CN=john,OU=Administrators,DC=TESTMYLDAP,DC=COM
LDAP_MANAGER_PASSWORD Password for the bind user.
LDAP_SEARCH_BASE DC=TESTMYLDAP,DC=COM
LDAP_SEARCH_FILTER Filter for search, for Microsoft Active Directory usually you can use sAMAccountName. (sAMAccountName={username})
LDAP_BIND_DN {username} Depends on your LDAP server the binding structure can change. This field support variable expansion for the username. {username}@TESTMYLDAP.com or UID={username},OU=PEOPLE,DC=TESTMYLDAP,DC=COM
LDAP_ALLOWED_USERS (Optional) Support a list separated by commas. 'diego,john,s-master'
LDAP_ALLOWED_GROUPS (Optional) Supports regular expressions, and support a list separated by commas. 'DevOps production environment', 'Developers .* environment'
LDAP_ALLOWED_GROUPS_CONDITIONAL and and, or Conditional to match all the groups in the list or just one of them. or
LDAP_ALLOWED_GROUPS_CASE_SENSITIVE enabled enabled, disabled Enabled or disabled case sensitive groups matches. disabled
LDAP_ALLOWED_GROUPS_USERS_CONDITIONAL or and, or Conditional to match user and at least one group in the list, or one of the two and
CACHE_EXPIRATION 5 Cache expiration time in minutes. 10
LOG_LEVEL INFO INFO, WARNING, ERROR Logger level. DEBUG
LOG_FORMAT TEXT TEXT, JSON Output format of the logger. JSON
LDAP_HTTPS_SUPPORT disabled enabled, disabled Enabled or disabled HTTPS support with self signed certificate.
BRUTE_FORCE_PROTECTION disabled enabled, disabled Enabled or disabled Brute force protection per IP.
BRUTE_FORCE_EXPIRATION 10 Brute force expiration time in seconds per IP.
BRUTE_FORCE_FAILURES 3 Number of failures before the IP is blocked.

HTTP request headers

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

  • Ldap-Endpoint
  • Ldap-Manager-Dn-Username
  • Ldap-Manager-Password
  • Ldap-Bind-DN
  • Ldap-Search-Base
  • Ldap-Search-Filter
  • Ldap-Allowed-Users
  • Ldap-Allowed-Groups
  • Ldap-Allowed-Groups-Case-Sensitive
  • Ldap-Allowed-Groups-Conditional

HTTP response headers

  • x-username Contains the authenticated username
  • x-groups Contains the username matches groups

Installation and configuration

The easy way to use Another LDAP Authentication is running as a Docker container and set the parameters via environment variables.

Step 1 - Run as a Docker container

Change the environment variables with your setup.

docker run -d \
    -e LDAP_ENDPOINT='ldaps://testmyldap.com:636' \
    -e LDAP_MANAGER_DN_USERNAME='CN=john-service-user,OU=Administrators,DC=TESTMYLDAP,DC=COM' \
    -e LDAP_MANAGER_PASSWORD='MasterpasswordNoHack123' \
    -e LDAP_BIND_DN='{username}@TESTMYLDAP.COM' \
    -e LDAP_SEARCH_BASE='DC=TESTMYLDAP,DC=COM' \
    -e LDAP_SEARCH_FILTER='(sAMAccountName={username})' \
    -e LOG_FORMAT='JSON' \
    -p 9000:9000 \
    --name another_ldap_auth \
    dignajar/another-ldap-auth:latest

Another LDAP Authentication now is running on http://localhost:9000.

Test it via curl:

curl -vvv http://localhost:9000 -u diego:mypassword

Output from ALDAP:

{"date": "2021-05-21 10:06:52", "level": "INFO", "objectName": "Cache", "ip": "192.168.0.10", "referrer": null, "message": "User not found in the cache.", "username": "diego"}
{"date": "2021-05-21 10:06:52", "level": "INFO", "objectName": "Aldap", "ip": "192.168.0.10", "referrer": null, "message": "Authenticating user.", "username": "diego", "finalUsername": "diego"}
{"date": "2021-05-21 10:06:53", "level": "INFO", "objectName": "Aldap", "ip": "192.168.0.10", "referrer": null, "message": "Authentication successful.", "username": "diego", "elapsedTime": "0.22335"}
{"date": "2021-05-21 10:06:53", "level": "INFO", "objectName": "Cache", "ip": "192.168.0.10", "referrer": null, "message": "Adding user to the cache.", "username": "diego"}
192.168.0.10 - - [21/May/2021 10:06:53] "GET / HTTP/1.1" 200 -

Remember you can enable self-signed certificate from Flask via the environment variable LDAP_HTTPS_SUPPORT=="enabled".

Step 2 - Nginx configuration

Nginx use the module ngx_http_auth_request_module to do the subrequest.

The following example shows how to configure Nginx that is running in the same machine as Another LDAP Authentication. The backend /private/ includes the authentication request to /another_ldap_auth.

location /private/ {
    auth_request /another_ldap_auth;
    # ...
    # Here you private site
}

location = /another_ldap_auth {
    internal;
    proxy_pass_request_body off;
    proxy_set_header Content-Length "";
    proxy_pass http://localhost:9000;
}

Now you can access to your website wich is going to be something like this http://myserver.com/private/ and Nginx will request you to write the username and password.

Deploy to Kubernetes with Nginx ingress controller

Get the K8s manifests from the folder /kubernetes.

The manifests for K8s helps to deploy Another LDAP Authentication in the namespace ingress-nginx and expose the service in the cluster at the following address https://another-ldap-auth.ingress-nginx.

Please change the environment variables from the manifest and the secret for the bind username.

After you have running Another LDAP Authentication in your Kubernetes, you can modify the ingress manifest from the application you want to protect.

You can remove the comment # and send headers as variables such as Matching groups.

---
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: demo-webserver
  namespace: demo
  annotations:
    nginx.ingress.kubernetes.io/auth-url: https://another-ldap-auth.ingress-nginx

    # nginx.ingress.kubernetes.io/auth-snippet: |
    #   proxy_set_header Ldap-Allowed-Groups "<SOME GROUP>";
    #   proxy_set_header Ldap-Allowed-Groups-Conditional "or";
spec:
  rules:
  - host: demo.local
    http:
      paths:
      - path: /
        backend:
          serviceName: demo-webserver
          servicePort: 80

Brute Force protection

Brute force protection is blocking user IP, please read this article to know the limitations about blocking IPs

Known limitations

  • Parameters via headers need to be escaped, for example, you can not send parameters such as $1 or $test because Nginx is applying variable expansion.

Breaking changes from v1.x to v2.x

  • LDAP_REQUIRED_GROUPS renamed to LDAP_ALLOWED_USERS
  • LDAP_REQUIRED_GROUPS_CONDITIONAL renamed to LDAP_ALLOWED_GROUPS_CONDITIONAL
  • LDAP_REQUIRED_GROUPS_CASE_SENSITIVE renamed to LDAP_ALLOWED_GROUPS_CASE_SENSITIVE
  • LDAP_SERVER_DOMAIN removed and replace by LDAP_BIND_DN

another-ldap-auth's People

Contributors

dignajar avatar hajowieland avatar indece-official avatar kirrmann avatar marianobilli avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar

another-ldap-auth's Issues

Groups lookup fails if a single match doesn't have the regular expression match

Let's take an example of a group as follows :

ipaUniqueID=blahblah-blah,cn=sudorules,cn=sudo,dc=blah,dc=net

When the search hits this particular group it will crash due to an AttributeError as the code doesn't handle it and not try to process the next set of groups. The error looks something like this:

  File "/home/aldap/main.py", line 200, in login
    validGroups, matchedGroups = cache.validateGroups(username, matchingGroups)
  File "/home/aldap/cache.py", line 98, in validateGroups
    matches = list(filter(None,list(map(self.__findMatch__, repeat(group), adGroups))))
  File "/home/aldap/cache.py", line 73, in __findMatch__
    adGroup = re.match('(?i)CN=((\w*\s?_?-?)*)', adGroup).group(1)
AttributeError: 'NoneType' object has no attribute 'group'

Support hyphen ("-") in group names

Currently group name that containes hyphens are handled incorrect:

Example:
a user has memberOf=CN=DE-APP-ACCESS,OU=Access,OU=Application,OU=Groups,OU=Client,OU=DE,DE=example,DC=com
--> Group name is handled as "DE" instead of "DE-APP-ACCESS".

It seems, that the regex in aldap.py / __find__match didn't include "-"
Should be
adGroup = re.match('CN=((\w*\s?_?-?]*)*)', adGroup).group(1)
instead of
adGroup = re.match('CN=((\w*\s?_?]*)*)', adGroup).group(1)

Some users in LDAP can be without groups

Hello everyone.
Some users in LDAP can be without groups. In that case app returns 500
I think it should return another status, not 500
Docker image: dignajar/another-ldap-auth:2.2.1

Stacktrace:
[2021-06-23 10:35:31,561] ERROR in app: Exception on / [GET] Traceback (most recent call last): File "/usr/local/lib/python3.9/site-packages/flask/app.py", line 2070, in wsgi_app response = self.full_dispatch_request() File "/usr/local/lib/python3.9/site-packages/flask/app.py", line 1515, in full_dispatch_request rv = self.handle_user_exception(e) File "/usr/local/lib/python3.9/site-packages/flask/app.py", line 1513, in full_dispatch_request rv = self.dispatch_request() File "/usr/local/lib/python3.9/site-packages/flask/app.py", line 1499, in dispatch_request return self.ensure_sync(self.view_functions[rule.endpoint])(**req.view_args) File "/usr/local/lib/python3.9/site-packages/flask_httpauth.py", line 161, in decorated user = self.authenticate(auth, password) File "/usr/local/lib/python3.9/site-packages/flask_httpauth.py", line 238, in authenticate return self.verify_password_callback(username, client_password) File "/home/aldap/main.py", line 202, in login validGroups, matchedGroups, adGroups = aldap.validateGroups(username, matchingGroups) File "/home/aldap/aldap.py", line 104, in validateGroups adGroups.extend(element['memberOf']) KeyError: 'memberOf'

Provide a helm chart repository as github page + register to artifacthub.com

Hi,

It would be nice to have the chart published in a repository so it's easy to use OOTB.
Hosting a repository is pretty straight forward with github pages feature.

There is a guide here https://helm.sh/docs/topics/chart_repository/#github-pages-example .
Took me about 1h to setup if I recall.

You can check what I did here and take what you need: https://github.com/ieugen/charts .

Also adding the helm repository to artifacthub.io is very simple after that.
Just sign in to artifacthub, then go to user menu (top right) / Control panel and then Add repository.
Put the same url you would use to helm add

CrashLoopBackOff when CACHE_EXPIRATION is configured.

I tried to configure CACHE_EXPIRATION via the environment in the latest docker image and the pod was crash looping.

Traceback (most recent call last):
  File "/opt/main.py", line 40, in <module>
    cache = Cache(CACHE_EXPIRATION)
  File "/opt/cache.py", line 8, in __init__
    self.validUntil = datetime.now() + timedelta(minutes=self.expirationMinutes)
TypeError: unsupported type for timedelta minutes component: str

This is the error.

I guess the problem is with the type conversion. You might want to convert the str value to an int

Support multiple ldap server listing to improve resiliency

Instead of connecting to a single LDAP server and if it fails for some reason not being able to support any services, it'd be really helpful to take a list of ldap servers to connect to in case the primary one goes down, where we get the can't connect error we should fall back to the list of secondaries.

This would be similar to the listing suppoorted by nginx-ldap-auth.. https://github.com/tiagoapimenta/nginx-ldap-auth/blob/master/config.sample.yaml#L3

Group listing always returns empty string

I have freeipa ldap server and authentication happens fine however listing groups for some reason always returns empty...

This is how i have configured the relevant parts of deployment

- name: LDAP_MANAGER_DN_USERNAME
  value: "uid=searchldap,cn=sysaccounts,cn=etc,dc=blah,dc=net"
- name: LDAP_BIND_DN
  value: "uid={username},cn=users,cn=accounts,dc=blah,dc=net"
- name: LDAP_SEARCH_BASE
  value: "cn=users,dc=blah,dc=net"
- name: LDAP_SEARCH_FILTER
  value: "(uid={username})"

Now this is how my user looks like:

dn: uid=stew,cn=users,cn=accounts,dc=blah,dc=net         
memberOf: cn=grafana-viewers,cn=groups,cn=accounts,dc=blah,dc=net                            

Here's my ingress annotations:

apiVersion: networking.k8s.io/v1beta1
kind: Ingress
metadata:
  annotations:
    nginx.ingress.kubernetes.io/auth-url: http://another-ldap-auth.ingress-nginx.svc.cluster.local:9000
    nginx.ingress.kubernetes.io/auth-snippet: |
      proxy_set_header Ldap-Allowed-Groups "grafana-viewers";
      proxy_set_header Ldap-Allowed-Groups-Conditional "and";

Here's the logs:

{"date": "2021-06-11 19:29:44", "level": "INFO", "objectName": "Cache", "ip": "10.226.64.3", "referrer": null, "message": "User not found in the cache for authentication.", "username": "aakarsh"}
{"date": "2021-06-11 19:29:44", "level": "INFO", "objectName": "Aldap", "ip": "10.226.64.3", "referrer": null, "message": "Authenticating user.", "username": "aakarsh", "finalUsername": "uid=aakarsh,cn=users,cn=accounts,dc=blah,dc=net"}
{"date": "2021-06-11 19:29:44", "level": "INFO", "objectName": "Aldap", "ip": "10.226.64.3", "referrer": null, "message": "Authentication successful.", "username": "aakarsh", "elapsedTime": "0.06220197677612305"}
{"date": "2021-06-11 19:29:44", "level": "INFO", "objectName": "Cache", "ip": "10.226.64.3", "referrer": null, "message": "Adding user to the cache.", "username": "aakarsh"}
{"date": "2021-06-11 19:29:44", "level": "INFO", "objectName": "Cache", "ip": "10.226.64.3", "referrer": null, "message": "Validating groups via cache.", "username": "aakarsh", "cacheGroups": ""}
{"date": "2021-06-11 19:29:44", "level": "WARNING", "objectName": "Cache", "ip": "10.226.64.3", "referrer": null, "message": "Invalid groups from cache.", "username": "aakarsh", "conditional": "and"}
{"date": "2021-06-11 19:29:44", "level": "INFO", "objectName": "Aldap", "ip": "10.226.64.3", "referrer": null, "message": "Search by filter.", "filter": "(uid=aakarsh)", "elapsedTime": "0.002645730972290039"}
{"date": "2021-06-11 19:29:44", "level": "INFO", "objectName": "Aldap", "ip": "10.226.64.3", "referrer": null, "message": "Validating groups.", "username": "aakarsh", "groups": "grafana-viewers", "conditional": "and"}
{"date": "2021-06-11 19:29:44", "level": "ERROR", "objectName": "Aldap", "ip": "10.226.64.3", "referrer": null, "message": "Invalid groups for the user.", "username": "aakarsh", "matchedGroups": "", "groups": "grafana-viewers", "conditional": "and"}

What is interesting is that if I try to use the nginx-ldap-auth image instead wiht following config itt works fine:

auth:
  bindDN: uid=searchldap,cn=sysaccounts,cn=etc,dc=blah,dc=net
  bindPW: blahpw
user:
  baseDN: cn=accounts,dc=blah,dc=net
  filter: "(uid={0})"
  requiredGroups:
  - grafana-viewers
group:
  baseDN: cn=accounts,dc=blah,dc=net
  groupAttr: cn
  filter: "(member={0})"

Can anyone please help me figure out the LDAP_SEARCH_BASE and LDAP_SEARCH_FILTER vars that i need to be rather using?

I;ve a tried a multitude of variations for LDAP_SEARCH_BASE and LDAP_SEARCH_FILTER but never succeeded..

Does it make sense to get rid of "Use a production WSGI server instead" ?

Hi,

While running the docker version of the app I got:

 * Environment: production
   WARNING: This is a development server. Do not use it in a production deployment.
   Use a production WSGI server instead.

Googleing that (not a python dev) I got to the bellow solution - to use waitress - a production ready wsgi server.
I think others exist as well.

if __name__ == "__main__":
    from waitress import serve
    serve(app, host="0.0.0.0", port=8080)

https://stackoverflow.com/questions/51025893/flask-at-first-run-do-not-use-the-development-server-in-a-production-environmen

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.