Ldap server integration

This commit is contained in:
Oliver Marks 2018-04-27 07:34:37 +01:00
parent 1ae785acf3
commit 54a90f8359
21 changed files with 425 additions and 6144 deletions

4
.gitignore vendored
View File

@ -8,4 +8,6 @@ staticfiles/*
cache/
celerybeat-schedule
celerybeat.pid
.minio.sys
.minio.sys
buckets
*.pyc

View File

@ -111,6 +111,14 @@ Setup / create new certs
letsencrypt certonly --renew --webroot -w /var/www/.well-known -d stage.maidstone-hackspace.org.uk
letsencrypt certonly --webroot -w /var/www/.well-known -d stage.maidstone-hackspace.org.uk
#+END_SRC
Automation of renewal process
create a file called =/etc/cron.monthly/letsencrypt-renew.sh= and make it executable with chmod +x, then place your above commands in the file like in the example below.
#+BEGIN_SRC bash
#!/bin/bash
letsencrypt certonly --webroot --renew-by-default --agree-tos -w /var/www/.well-known -d stage.maidstone-hackspace.org.uk
#+END_SRC
*** Postgres tips
Connect to the database inside container to run sql commands.
#+BEGIN_SRC bash
@ -139,3 +147,17 @@ letsencrypt config
CMD ["nginx", "-g", "daemon off;"]
sudo chmod -R a+rX static/
#+BEGIN_SRC emacs-lisp
(let ((default-directory "/docker:hackdev_django_1:/app"))
(python-shell-make-comint "python manage.py shell" "Python" 'show))
#+END_SRC
** Test
#+BEGIN_SRC emacs-lisp
(setq python-shell-interpreter "/docker:hackdev_django_1:/usr/local/bin/python")
#+END_SRC
#+RESULTS:
: /docker:hackdev_django_1:/usr/local/bin/python

View File

@ -144,6 +144,7 @@ LOCAL_APPS = (
'mhackspace.core',
'mhackspace.requests',
'mhackspace.register',
'mhackspace.ldapsync',
'mhackspace.rfid',
)
@ -344,10 +345,10 @@ AUTH_PASSWORD_VALIDATORS = [
# PASSWORD HASHING
PASSWORD_HASHERS = [
'django.contrib.auth.hashers.BCryptSHA256PasswordHasher',
'django.contrib.auth.hashers.Argon2PasswordHasher',
'django.contrib.auth.hashers.PBKDF2PasswordHasher',
'django.contrib.auth.hashers.PBKDF2SHA1PasswordHasher',
'django.contrib.auth.hashers.BCryptSHA256PasswordHasher',
'django.contrib.auth.hashers.BCryptPasswordHasher',
]
@ -538,3 +539,7 @@ CACHES = {
'TIMEOUT': None
}
}
LDAP_SERVER = env('LDAP_SERVER', default='172.19.0.6')
LDAP_PASSWORD = env('LDAP_ADMIN_PASSWORD', default='secretldappassword')
LDAP_ROOT = env('LDAP_ROOT', default='dc=maidstone-hackspace, dc=org, dc=uk')

View File

@ -12,7 +12,7 @@ TEMPLATES[0]['OPTIONS']['debug'] = DEBUG
# INTERNAL_IPS = ['127.0.0.1', '10.0.2.2', '172.22.0.9', '192.168.1.113', '172.22.0.4', '0.0.0.0']
# tricks to have debug toolbar when developing with docker
if os.environ.get('USE_DOCKER') == 'yes':
ip = socket.gethostbyname('nginx')
# ip = socket.gethostbyname('nginx')
INTERNAL_IPS += [ip[:-1] + "1"]
ip = socket.gethostbyname(socket.gethostname())
INTERNAL_IPS += [ip[:-1] + "1"]

View File

@ -1,5 +1,7 @@
#rename me to .env, and change per environment, .env should not be commited contains
#sensitive settings
#Python config
PYTHONPATH=/app/
#for dev only
USE_DOCKER_DEBUG=yes
@ -58,3 +60,10 @@ MATRIX_PASSWORD=
MINIO_ACCESS_KEY=thing
MINIO_SECRET_KEY=thing
# LDAP Service
LDAP_ORGANISATION=Maidstone Hackspace
LDAP_DOMAIN=maidstone-hackspace.org.uk
LDAP_ADMIN_PASSWORD=secret-ldap-password
LDAP_SERVER=directory
LDAP_ROOT=dc=maidstone-hackspace, dc=org, dc=uk

View File

@ -15,6 +15,7 @@ services:
- postgres_data:/var/lib/postgresql/data
- postgres_backup:/backups
env_file: .env
restart: always
django:
build:
@ -28,9 +29,11 @@ services:
volumes:
- .:/app
- sockets:/data/sockets
restart: always
redis:
image: redis:latest
restart: always
celeryworker:
build:
@ -43,6 +46,7 @@ services:
- postgres
- redis
command: celery -A mhackspace.celeryapp worker -l INFO
restart: always
celerybeat:
build:
@ -55,3 +59,7 @@ services:
- postgres
- redis
command: celery -A mhackspace.celeryapp beat -l INFO
restart: always
directory:
image: osixia/openldap:1.2.0
env_file: .env

View File

@ -105,3 +105,7 @@ services:
volumes:
- ./mhackspace:/data
command: server --config-dir /tmp/minio /data
directory:
image: osixia/openldap:1.2.0
env_file: .env

View File

@ -40,3 +40,4 @@ docker-compose -f dev.yml run django python manage.py migrate
#+BEGIN_SRC sh
docker-compose -f dev.yml run django python manage.py createsuperuser
#+END_SRC

View File

@ -42,7 +42,7 @@ class Command(BaseCommand):
feed = AutoFixture(Feed)
feed.create(10)
post = AutoFixture(Post, follow_fk=True)
post = AutoFixture(Post, generate_fk=True)
post.create(10)
categorys = AutoFixture(Category)

171
mhackspace/ldap.org Normal file
View File

@ -0,0 +1,171 @@
#+TITLE: Hackspace LDAP examples
* Introduction
LDAP is a direcotry service that is often used to authenticate users, it has support in a lot of apps so we maintain a ldap server to provided authentication.
** Usefull links
https://www.redpill-linpro.com/techblog/2016/08/16/ldap-password-hash.html
** setup
Setup an ldap server inside docker
#+BEGIN_SRC bash
docker run --name hackspace-ldap --restart=always --env LDAP_ORGANISATION="Maidstone Hackspace" --env LDAP_DOMAIN="maidstone-hackspace.org.uk" --env LDAP_ADMIN_PASSWORD="JonSn0w" --detach osixia/openldap:1.2.0
#+END_SRC
* Add Objects
** Add Organizational Units
#+BEGIN_SRC python :results value
from ldap3 import Server, Connection, ObjectDef, AttrDef, Reader, Writer, ALL
LDAP_PASSWORD = 'secretldappassword'
LDAP_ROOT = 'dc=maidstone-hackspace, dc=org, dc=uk'
LDAP_SERVER = '172.18.0.2'
server = Server(LDAP_SERVER)
conn = Connection(server, 'cn=admin, %s' % LDAP_ROOT, LDAP_PASSWORD, auto_bind=True)
conn.add('ou=users,dc=maidstone-hackspace, dc=org, dc=uk', 'organizationalUnit')
conn.add('ou=groups,dc=maidstone-hackspace, dc=org, dc=uk', 'organizationalUnit')
return conn.result
#+END_SRC
#+RESULTS:
| result | : | 0 | description | : | success | dn | : | | message | : | | referrals | : | hline | type | : | addResponse |
** Add Groups
#+BEGIN_SRC python :results value
from ldap3 import Server, Connection, ObjectDef, AttrDef, Reader, Writer, ALL
LDAP_PASSWORD = 'secretldappassword'
LDAP_ROOT = 'dc=maidstone-hackspace, dc=org, dc=uk'
LDAP_SERVER = '172.18.0.2'
server = Server(LDAP_SERVER)
conn = Connection(server, 'cn=admin, %s' % LDAP_ROOT, LDAP_PASSWORD, auto_bind=True)
g = {'objectClass': ['groupOfNames', 'top'], 'cn': 'g1', 'member': ['cn=admin',]}
conn.add('cn=g1, ou=groups,dc=maidstone-hackspace, dc=org, dc=uk', attributes=g)
conn.add('cn=g2, ou=groups,dc=maidstone-hackspace, dc=org, dc=uk', attributes=g)
return conn.result
#+END_SRC
#+RESULTS:
| result | : | 0 | description | : | success | dn | : | | message | : | | referrals | : | hline | type | : | addResponse |
** Add Users
#+BEGIN_SRC python :results value
from ldap3 import Server, Connection, ObjectDef, AttrDef, Reader, Writer, ALL
LDAP_PASSWORD = 'secretldappassword'
LDAP_ROOT = 'dc=maidstone-hackspace, dc=org, dc=uk'
LDAP_SERVER = '172.18.0.2'
server = Server(LDAP_SERVER)
conn = Connection(server, 'cn=admin, %s' % LDAP_ROOT, LDAP_PASSWORD, auto_bind=True)
u = {'objectClass': ['inetOrgPerson', 'person', 'top'], 'sn': 'user_sn', 'cn': 'First Last', 'userPassword': ''}
conn.add('cn=user2,ou=users,dc=maidstone-hackspace, dc=org, dc=uk', attributes=u)
return conn.result
#+END_SRC
#+RESULTS:
| result | : | 0 | description | : | success | dn | : | | message | : | | referrals | : | hline | type | : | addResponse |
* Modify objects
** Modify groups
#+BEGIN_SRC python :results raw
from ldap3 import Server, Connection, ObjectDef, AttrDef, Reader, Writer, ALL, MODIFY_REPLACE, MODIFY_DELETE
server = Server('172.17.0.2')
conn = Connection(server, 'cn=admin, dc=maidstone-hackspace, dc=org, dc=uk', 'JonSn0w', auto_bind=True)
print(conn.bind())
conn.modify(
'cn=g1, ou=groups,dc=maidstone-hackspace, dc=org, dc=uk',
{'member': [
(MODIFY_REPLACE, ['cn=admin','cn=user1'])]})
return conn.result
#+END_SRC
#+RESULTS:
{'result': 0, 'description': 'success', 'dn': '', 'message': '', 'referrals': None, 'type': 'modifyResponse'}
* Search objects
** Check group exists
#+BEGIN_SRC python :results value
from ldap3 import Server, Connection, ObjectDef, AttrDef, Reader, Writer, ALL
server = Server('172.17.0.2')
conn = Connection(server, 'cn=admin, dc=maidstone-hackspace, dc=org, dc=uk', 'JonSn0w', auto_bind=True)
print(conn.bind())
return conn.search('cn=g4, ou=groups, dc=maidstone-hackspace, dc=org, dc=uk', '(objectclass=groupOfNames)')
#+END_SRC
#+RESULTS:
: False
* List objects
** list users
#+BEGIN_SRC python :results value
from ldap3 import Server, Connection, ObjectDef, AttrDef, Reader, Writer, ALL
server = Server('172.19.0.6')
conn = Connection(server, 'cn=admin, dc=maidstone-hackspace, dc=org, dc=uk', 'secretldappassword', auto_bind=True)
print(conn.bind())
conn.search('dc=maidstone-hackspace, dc=org, dc=uk', '(objectclass=person)')
return conn.entries
#+END_SRC
#+RESULTS:
| DN: | cn=oly | ou=users | dc=maidstone-hackspace | dc=org | dc=uk | - | STATUS: | Read | - | READ | TIME: | 2018-04-23T21:13:46.919782 | DN: | cn=test | ou=users | dc=maidstone-hackspace | dc=org | dc=uk | - | STATUS: | Read | - | READ | TIME: | 2018-04-23T21:13:46.919828 |
** list groups
#+BEGIN_SRC python :results table
from ldap3 import Server, Connection, ObjectDef, AttrDef, Reader, Writer, ALL, SUBTREE
server = Server('172.17.0.2')
conn = Connection(server, 'cn=admin, dc=maidstone-hackspace, dc=org, dc=uk', 'JonSn0w', auto_bind=True)
print(conn.bind())
conn.search(
search_base='ou=groups, dc=maidstone-hackspace, dc=org, dc=uk',
search_filter='(objectclass=groupOfNames)',
search_scope=SUBTREE,
attributes=['cn', 'member'])
return conn.entries[0]
#+END_SRC
** list organizational units
#+BEGIN_SRC python :results table
from ldap3 import Server, Connection, ObjectDef, AttrDef, Reader, Writer, ALL
server = Server('172.17.0.2')
conn = Connection(server, 'cn=admin, dc=maidstone-hackspace, dc=org, dc=uk', 'JonSn0w', auto_bind=True)
print(conn.bind())
conn.search('dc=maidstone-hackspace, dc=org, dc=uk', '(objectclass=organizationalUnit)')
return conn.entries
#+END_SRC
#+RESULTS:
| DN: | ou=users | dc=maidstone-hackspace | dc=org | dc=uk | - | STATUS: | Read | - | READ | TIME: | 2018-04-19T22:29:32.989385 | DN: | ou=groups | dc=maidstone-hackspace | dc=org | dc=uk | - | STATUS: | Read | - | READ | TIME: | 2018-04-19T22:29:32.989433 |
#+BEGIN_SRC python :results table
from ldap3 import Server, Connection, ObjectDef, AttrDef, Reader, Writer, ALL
server = Server('172.19.0.3')
conn = Connection(server, 'cn=admin, dc=maidstone-hackspace, dc=org, dc=uk', 'secretldappassword', auto_bind=True)
print(conn.bind())
conn.search('dc=maidstone-hackspace, dc=org, dc=uk', '(objectclass=organizationalUnit)')
return conn.entries
#+END_SRC
#+RESULTS:
| |
* Queries
Testing with elisp
#+BEGIN_SRC emacs-lisp
(setq ldap-default-host "test.maidstone-hackspace.org.uk")
(setq ldap-default-base "dc=test, dc=maidstone-hackspace, dc=org, dc=uk")
(setq ldap-host-alist '(("ldap://test.maidstone-hackspace.org.uk"
timelimit "10"
password "password-here"
base "dc=test, dc=maidstone-hackspace, dc=org, dc=uk"
binddn "cn=admin, dc=test, dc=maidstone-hackspace, dc=org, dc=uk")))
(ldap-search "(objectclass=person)")
#+END_SRC
Testing with ldap search
#+BEGIN_SRC shell
docker exec hackstage_directory_1 ldapsearch -x -H ldap://localhost -b "dc=test, dc=maidstone-hackspace, dc=org, dc=uk" -D "cn=admin, dc=test, dc=maidstone-hackspace, dc=org, dc=uk" -w password-here
#+END_SRC

View File

@ -0,0 +1,3 @@
from mhackspace.celeryapp import app as celery_app
__all__ = ['celery_app']

View File

@ -0,0 +1,22 @@
from django.contrib.auth.models import Group
from django.forms.models import model_to_dict
from django.core.management.base import BaseCommand
from mhackspace.subscriptions.payments import select_provider
from mhackspace.users.models import Membership, User
from mhackspace.subscriptions.helper import create_or_update_membership
from django.contrib.auth.models import Group
#from ldap3 import Server, Connection, ObjectDef, AttrDef, Reader, Writer, ALL
from celery import shared_task
from .models import User
class Command(BaseCommand):
help = 'Sync database directory'
def handle(self, *args, **options):
for user in User.objects.all():
self.stdout.write(self.style.NOTICE('test %s' % user.username))
for group in Group.objects.all():
self.stdout.write(self.style.NOTICE('test %s' % group.name))

View File

@ -0,0 +1,20 @@
from django.core.management.base import BaseCommand
from mhackspace.ldapsync.tasks import (
ldap_list_users, ldap_list_groups, ldap_list_organizational_units, conn)
class Command(BaseCommand):
help = 'List database directory'
def handle(self, *args, **options):
self.stdout.write(self.style.NOTICE('Listing Organizational units.....'))
for row in ldap_list_organizational_units(conn):
self.stdout.write(self.style.NOTICE('\t%s' % row.entry_dn))
self.stdout.write(self.style.NOTICE('Listing Users.....'))
for row in ldap_list_users(conn):
self.stdout.write(self.style.NOTICE('\t%s' % row.entry_dn))
self.stdout.write(self.style.NOTICE('Listing Groups.....'))
for row in ldap_list_groups(conn):
self.stdout.write(self.style.NOTICE('\t%s' % row.entry_dn))

View File

@ -0,0 +1,35 @@
from django.contrib.auth.models import Group
from django.core.management.base import BaseCommand
from mhackspace.users.models import User
from mhackspace.ldapsync.tasks import (
ldap_add_user, ldap_add_group, ldap_add_organizational_unit, conn)
class Command(BaseCommand):
help = 'Sync database directory'
def handle(self, *args, **options):
self.stdout.write(self.style.NOTICE('Loading Organizational unit.....'))
ldap_add_organizational_unit(conn, 'users')
ldap_add_organizational_unit(conn, 'groups')
self.stdout.write(self.style.NOTICE('Loading Users.....'))
for user in User.objects.all():
result = ldap_add_user(
conn,
username=user.username,
password=user.password)
if result.get('message'):
self.stdout.write(result.get('message'))
self.stdout.write(self.style.NOTICE('\ttest %s' % user.username))
self.stdout.write(self.style.NOTICE('Loading Groups.....'))
for group in Group.objects.all():
result = ldap_add_group(conn, group.name, ['admin', ])
if result.get('message'):
self.stdout.write(result.get('message'))
self.stdout.write(self.style.NOTICE('\ttest %s' % group.name))

View File

@ -0,0 +1,103 @@
from django.conf import settings
from django.contrib.auth.models import Group
from ldap3 import Server, Connection, ObjectDef, AttrDef, Reader, Writer, ALL
from celery import shared_task
import json
server = Server(settings.LDAP_SERVER)
conn = Connection(
server,
'cn=admin, dc=maidstone-hackspace, dc=org, dc=uk',
settings.LDAP_PASSWORD,
auto_bind=True)
def ldap_list_organizational_units(connection):
connection.search(
'%s' % (settings.LDAP_ROOT),
'(objectclass=organizationalUnit)')
for result in connection.entries:
yield result
def ldap_list_groups(connection):
connection.search(
'%s' % (settings.LDAP_ROOT),
'(objectclass=groupOfNames)')
for result in connection.entries:
yield result
def ldap_list_users(connection):
connection.search(
'%s' % (settings.LDAP_ROOT),
'(objectclass=person)')
for result in connection.entries:
yield result
@shared_task
def ldap_add_organizational_unit(connection, name):
exists = connection.search(
'cn=%s, %s' % (name, settings.LDAP_ROOT),
'(objectclass=organizationalUnit)')
if exists is False:
connection.add(
'ou=%s, %s' % (name, settings.LDAP_ROOT),
'organizationalUnit')
return connection.result
@shared_task
def ldap_add_group(connection, group, users):
exists = connection.search(
'cn=%s, ou=groups, %s' % (group, settings.LDAP_ROOT),
'(objectclass=groupOfNames)')
cn_list = ['cn=' + u for u in users]
g = {'objectClass': ['groupOfNames', 'top'], 'cn': group, 'member': cn_list}
if exists is False:
connection.add(
'cn=%s, ou=groups, %s' % (group, settings.LDAP_ROOT),
attributes=g)
return connection.result
@shared_task
def ldap_add_user(connection, username, name='', password=None):
u = {'objectClass': ['inetOrgPerson', 'person', 'top'], 'sn': 'user_sn', 'cn': 'First Last', 'userPassword': ''}
if not password:
return
exists = connection.search(
'cn=%s, ou=users, %s' % (username, settings.LDAP_ROOT),
'(objectclass=inetOrgPerson)')
u = {
'objectClass': ['inetOrgPerson', 'person', 'top'],
'sn': 'user_sn',
'cn': 'First Last name',
'userPassword': password,
}
if exists is False:
connection.add(
'cn=%s, ou=users, %s' % (username, settings.LDAP_ROOT),
attributes=u)
return connection.result
@shared_task
def complete_directory_sync(self):
server = Server(settings.LDAP_SERVER)
conn = Connection(
server,
'cn=admin, dc=maidstone-hackspace, dc=org, dc=uk',
settings.LDAP_PASSWORD,
auto_bind=True)
for user in User.objects.all():
ldap_add_user(conn, user.username)

View File

@ -55,8 +55,8 @@ def send_topic_update_email(sender, instance, **kwargs):
matrix_message.delay(
prefix=' - REQUEST',
message='%s - https://%s%s' % (
Site.objects.get_current().domain,
instance.title,
Site.objects.get_current().domain,
instance.get_absolute_url()))

File diff suppressed because one or more lines are too long

View File

@ -100,4 +100,6 @@ django-celery-beat==1.0.1
argon2-cffi
django-cors-headers
magic
python-magic
ldap3
bcrypt==3.1.4

View File

@ -15,6 +15,7 @@ services:
- postgres_data:/var/lib/postgresql/data
- postgres_backup:/backups
env_file: .env
restart: always
django:
build:
@ -29,9 +30,11 @@ services:
volumes:
- .:/app
- sockets:/data/sockets
restart: always
redis:
image: redis:latest
restart: always
celeryworker:
build:
@ -44,6 +47,7 @@ services:
- postgres
- redis
command: celery -A mhackspace.celeryapp worker -l INFO
restart: always
celerybeat:
build:
@ -56,3 +60,10 @@ services:
- postgres
- redis
command: celery -A mhackspace.celeryapp beat -l INFO
restart: always
directory:
image: osixia/openldap:1.2.0
ports:
- "0.0.0.0:389:389"
env_file: .env