Skip to content

Commit

Permalink
fix for groups and case sensitive, enable https support, improve readme
Browse files Browse the repository at this point in the history
  • Loading branch information
dignajar committed Jan 5, 2021
1 parent c3b3313 commit 4968624
Show file tree
Hide file tree
Showing 4 changed files with 42 additions and 16 deletions.
6 changes: 4 additions & 2 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,13 @@ ENV LDAP_SEARCH_BASE=""
ENV LDAP_SEARCH_FILTER=""
ENV LDAP_REQUIRED_GROUPS=""
ENV LDAP_REQUIRED_GROUPS_CONDITIONAL="and"
ENV LDAP_REQUIRED_GROUPS_CASE_SENSITIVE="enabled"
ENV LDAP_HTTPS_SUPPORT="disabled"

ENV PYTHONUNBUFFERED=0

RUN apk --no-cache add build-base openldap-dev
RUN pip install --no-cache-dir flask Flask-HTTPAuth python-ldap
RUN apk --no-cache add build-base openldap-dev libffi-dev
RUN pip install --no-cache-dir flask Flask-HTTPAuth python-ldap pyopenssl
COPY files/* /opt/

EXPOSE 9000
Expand Down
16 changes: 13 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,20 @@

**Another LDAP Authentication** is an implementation of the `ldap-auth-daemon` services described in the official blog from Nginx in the [following article](https://www.nginx.com/blog/nginx-plus-authenticate-users/).

**Another LDAP Authentication** it's prepared to run inside a Docker container, also you can run the Python script without the Docker container. Supports `ldap` and `ldaps` and provide a simple cache.
**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](https://img.shields.io/badge/Docker-Hub-blue.svg)](https://hub.docker.com/r/dignajar/another-ldap-auth)

[![Kubernetes](https://img.shields.io/badge/Kubernetes-Deployment-blue.svg)](https://github.com/dignajar/another-ldap-auth#deploy-in-kubernetes-with-nginx-ingress-controller)
[![Kubernetes YAML manifests](https://img.shields.io/badge/Kubernetes-Deployment-blue.svg)](https://github.com/dignajar/another-ldap-auth/tree/master/kubernetes)

## Features
- Supports `ldap` and `ldaps`.
- Provide a cache for users, you can limit the time of the cache.
- Supports validation groups.
- Supports validation groups with conditionals and regex.
- Supports configuration via headers or via environment variables.
- Supports HTTP response headers such as username and matched groups.
- Log format in Plain-Text or JSON.

## Diagram
![Another LDAP Authentication](https://i.ibb.co/Fn1ncbP/another-ldap-authentication.jpg)
Expand All @@ -33,8 +42,9 @@ All values type are `string`.
| LDAP_REQUIRED_GROUPS_CONDITIONAL | `and` | `and`, `or` | Conditional to match all the groups in the list or just one of them. | `or` |
| LDAP_REQUIRED_GROUPS_CASE_SENSITIVE | `enabled` | `enabled`, `disabled` | Enabled or disabled case sensitive groups matches. | `disabled` |
| CACHE_EXPIRATION | `5` | | Cache expiration time in minutes. | `10` |
| LOG_LEVEL | `INFO` | `DEBUG`, `INFO`, `WARN`, `ERROR` | Logger level. | `DEBUG` |
| 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. | |

### HTTP request headers
The variables send via HTTP headers take precedence over environment variables.
Expand Down
12 changes: 6 additions & 6 deletions files/aldap.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ def findMatch(self, group:str, ADGroup:str):
ADGroup = re.match('CN=((\w*\s?_?]*)*)', ADGroup).group(1)

if not self.groupCaseSensitive:
group = group.lower()
ADGroup = ADGroup.lower()

# return match against supplied group/pattern (None if there is no match)
try:
Expand Down Expand Up @@ -110,21 +110,21 @@ def validateGroups(self, groups):
matchesByGroup.append((group,matches))
matchedGroups.extend(matches)

self.logs.info({'message':'Validating groups.', 'matchedGroups': ','.join(matchedGroups), 'groups': ','.join(groups), 'conditional': self.groupConditional})
self.logs.info({'message':'Validating groups.', 'username': self.username, 'matchedGroups': ','.join(matchedGroups), 'groups': ','.join(groups), 'conditional': self.groupConditional})

# Conditiona OR, true if just 1 group match
if self.groupConditional == 'or':
if matchedGroups:
self.logs.info({'message':'At least one group is valid for the user.', 'matchedGroups': ','.join(matchedGroups), 'groups': ','.join(groups), 'conditional': self.groupConditional})
self.logs.info({'message':'At least one group is valid for the user.', 'username': self.username, 'matchedGroups': ','.join(matchedGroups), 'groups': ','.join(groups), 'conditional': self.groupConditional})
return True,matchedGroups
# Conditiona AND, true if all the groups match
elif self.groupConditional == 'and':
if len(groups) == len(matchesByGroup):
self.logs.info({'message':'All groups are valid for the user.', 'matchedGroups': ','.join(matchedGroups), 'groups': ','.join(groups), 'conditional': self.groupConditional})
self.logs.info({'message':'All groups are valid for the user.', 'username': self.username, 'matchedGroups': ','.join(matchedGroups), 'groups': ','.join(groups), 'conditional': self.groupConditional})
return True,matchedGroups
else:
self.logs.warning({'message':'Invalid group conditional.', 'conditional': self.groupConditional})
self.logs.error({'message':'Invalid group conditional.', 'username': self.username, 'conditional': self.groupConditional})
return False,[]

self.logs.error({'message':'Invalid groups for the user.', 'username': self.username, 'matchedGroups': ','.join(matchedGroups), 'groups': ','.join(groups), 'conditional': self.groupConditional})
return False,[]

24 changes: 19 additions & 5 deletions files/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,17 +70,23 @@ def login(username, password):
LDAP_REQUIRED_GROUPS_CONDITIONAL = environ["LDAP_REQUIRED_GROUPS_CONDITIONAL"]

# The default is "enabled", another option is "disabled"
LDAP_REQUIRED_GROUPS_CASE_SENSITIVE = "enabled"
LDAP_REQUIRED_GROUPS_CASE_SENSITIVE = True
if "Ldap-Required-Groups-Case-Sensitive" in request.headers:
LDAP_REQUIRED_GROUPS_CASE_SENSITIVE = request.headers["Ldap-Required-Groups-Case-Sensitive"] =='enabled'
LDAP_REQUIRED_GROUPS_CASE_SENSITIVE = (request.headers["Ldap-Required-Groups-Case-Sensitive"] == "enabled")
elif "LDAP_REQUIRED_GROUPS_CASE_SENSITIVE" in environ:
LDAP_REQUIRED_GROUPS_CASE_SENSITIVE = environ["LDAP_REQUIRED_GROUPS_CASE_SENSITIVE"] =='enabled'
LDAP_REQUIRED_GROUPS_CASE_SENSITIVE = (environ["LDAP_REQUIRED_GROUPS_CASE_SENSITIVE"] == "enabled")

LDAP_SERVER_DOMAIN = ""
if "Ldap-Server-Domain" in request.headers:
LDAP_SERVER_DOMAIN = request.headers["Ldap-Server-Domain"]
elif "LDAP_SERVER_DOMAIN" in environ:
LDAP_SERVER_DOMAIN = environ["LDAP_SERVER_DOMAIN"]

LDAP_HTTPS_SUPPORT = False
if "Ldap-Http-Support" in request.headers:
LDAP_HTTPS_SUPPORT = (request.headers["Ldap-Http-Support"] == "disabled")
elif "LDAP_HTTPS_SUPPORT" in environ:
LDAP_HTTPS_SUPPORT = (environ["LDAP_HTTPS_SUPPORT"] == "disabled")
except KeyError as e:
logs.error({'message': 'Invalid parameter'})
return False
Expand Down Expand Up @@ -115,7 +121,8 @@ def login(username, password):
groups = LDAP_REQUIRED_GROUPS.split(",") # Split the groups by comma and trim
groups = [x.strip() for x in groups] # Remove spaces
if not LDAP_REQUIRED_GROUPS_CASE_SENSITIVE:
groups = groups.lower()
groups = [x.lower() for x in groups] # Convert to lowercase
print(groups)
validGroups, matchesGroups = aldap.validateGroups(groups)
if not validGroups:
return False
Expand All @@ -137,4 +144,11 @@ def index(path):

# Main
if __name__ == '__main__':
app.run(host='0.0.0.0', port=9000, debug=False)
LDAP_HTTPS_SUPPORT = False
if "LDAP_HTTPS_SUPPORT" in environ:
LDAP_HTTPS_SUPPORT = (environ["LDAP_HTTPS_SUPPORT"] == "enabled")

if LDAP_HTTPS_SUPPORT:
app.run(host='0.0.0.0', port=9000, debug=False, ssl_context='adhoc')
else:
app.run(host='0.0.0.0', port=9000, debug=False)

0 comments on commit 4968624

Please sign in to comment.