From d417cfb8dc2584940420e28dffce196addfc6d17 Mon Sep 17 00:00:00 2001 From: Ivan Filippov Date: Mon, 11 Apr 2016 06:11:02 -0600 Subject: [PATCH 01/11] Initial support for LDAP group based security. --- README.md | 8 ++++++++ app/models.py | 39 +++++++++++++++++++++++++++++++++++---- config_template.py | 3 +++ 3 files changed, 46 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index aeeb307..086b011 100644 --- a/README.md +++ b/README.md @@ -53,6 +53,14 @@ Web application configuration is stored in `config.py` file. Let's clone it from (flask)$ vim config.py ``` +You can configure group based security by tweaking the below parameters in `config.py`. Groups membership comes from LDAP. +Setting `LDAP_GROUP_SECURITY` to True enables group-based security. With this enabled only members of the two groups listed below are allowed to login. Members of `LDAP_ADMIN_GROUP` will get the Administrator role and members of `LDAP_USER_GROUP` will get the User role. Sample config below: +``` +LDAP_GROUP_SECURITY = True +LDAP_ADMIN_GROUP = 'CN=PowerDNS-Admin Admin,OU=Custom,DC=ivan,DC=local' +LDAP_USER_GROUP = 'CN=PowerDNS-Admin User,OU=Custom,DC=ivan,DC=local' +``` + Create database after having proper configs ``` (flask)% ./createdb.py diff --git a/app/models.py b/app/models.py index 48ed39a..84093d4 100644 --- a/app/models.py +++ b/app/models.py @@ -19,6 +19,9 @@ LDAP_USERNAME = app.config['LDAP_USERNAME'] LDAP_PASSWORD = app.config['LDAP_PASSWORD'] LDAP_SEARCH_BASE = app.config['LDAP_SEARCH_BASE'] LDAP_TYPE = app.config['LDAP_TYPE'] +LDAP_GROUP_SECURITY = app.config['LDAP_GROUP_SECURITY'] +LDAP_ADMIN_GROUP = app.config['LDAP_ADMIN_GROUP'] +LDAP_USER_GROUP = app.config['LDAP_USER_GROUP'] PDNS_STATS_URL = app.config['PDNS_STATS_URL'] PDNS_API_KEY = app.config['PDNS_API_KEY'] @@ -172,6 +175,25 @@ class User(db.Model): try: ldap_username = result[0][0][0] l.simple_bind_s(ldap_username, self.password) + if LDAP_GROUP_SECURITY: + try: + groupSearchFilter = "(&(objectcategory=group)(member=%s))" % ldap_username + groups = self.ldap_search(groupSearchFilter, LDAP_SEARCH_BASE) + allowedlogin = False + isadmin = False + for group in groups: + if (group[0][0] == LDAP_ADMIN_GROUP): + allowedlogin = True + isadmin = True + logging.info('User %s is part of the "%s" group that allows admin access to PowerDNS-Admin' % (self.username,LDAP_ADMIN_GROUP)) + if (group[0][0] == LDAP_USER_GROUP): + allowedlogin = True + logging.info('User %s is part of the "%s" group that allows user access to PowerDNS-Admin' % (self.username,LDAP_USER_GROUP)) + if allowedlogin == False: + logging.error('User %s is not part of the "%s" or "%s" groups that allow access to PowerDNS-Admin' % (self.username,LDAP_ADMIN_GROUP,LDAP_USER_GROUP)) + return False + except: + logging.error('LDAP group lookup for user %s has failed' % self.username) logging.info('User "%s" logged in successfully' % self.username) # create user if not exist in the db @@ -185,17 +207,26 @@ class User(db.Model): self.firstname = self.username self.lastname = '' - # first register user will be in Administrator role - if User.query.count() == 0: + # first registered user will be in Administrator role or if part of LDAP Admin group + if (User.query.count() == 0): self.role_id = Role.query.filter_by(name='Administrator').first().id else: - self.role_id = Role.query.filter_by(name='User').first().id + self.role_id = Role.query.filter_by(name='User').first().id + + # + if LDAP_GROUP_SECURITY: + if isadmin == True: + self.role_id = Role.query.filter_by(name='Administrator').first().id self.create_user() logging.info('Created user "%s" in the DB' % self.username) + else: + # user already exists in database, set their admin status based on group membership (if enabled) + if LDAP_GROUP_SECURITY: + self.set_admin(isadmin) return True except: - logging.error('User "%s" input a wrong password' % self.username) + logging.error('User "%s" input a wrong password(stage2)' % self.username) return False else: logging.error('Unsupported authentication method') diff --git a/config_template.py b/config_template.py index 12a3ca7..e5a0484 100644 --- a/config_template.py +++ b/config_template.py @@ -27,6 +27,9 @@ LDAP_USERNAME = 'cn=dnsuser,ou=users,ou=services,dc=duykhanh,dc=me' LDAP_PASSWORD = 'dnsuser' LDAP_SEARCH_BASE = 'ou=System Admins,ou=People,dc=duykhanh,dc=me' LDAP_TYPE = 'ldap' // or 'ad' +LDAP_GROUP_SECURITY = False // or True +LDAP_ADMIN_GROUP = 'CN=PowerDNS-Admin Admin,OU=Custom,DC=ivan,DC=local' +LDAP_USER_GROUP = 'CN=PowerDNS-Admin User,OU=Custom,DC=ivan,DC=local' # POWERDNS CONFIG PDNS_STATS_URL = 'http://172.16.214.131:8081/' From 05944e8585c846609c1e70bbfa2a47622c55aedc Mon Sep 17 00:00:00 2001 From: Ivan Filippov Date: Mon, 11 Apr 2016 10:22:40 -0600 Subject: [PATCH 02/11] Don't require LDAP group parameters if LDAP_GROUP_SECURITY is not chosen --- app/models.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/app/models.py b/app/models.py index 84093d4..811a151 100644 --- a/app/models.py +++ b/app/models.py @@ -20,8 +20,9 @@ LDAP_PASSWORD = app.config['LDAP_PASSWORD'] LDAP_SEARCH_BASE = app.config['LDAP_SEARCH_BASE'] LDAP_TYPE = app.config['LDAP_TYPE'] LDAP_GROUP_SECURITY = app.config['LDAP_GROUP_SECURITY'] -LDAP_ADMIN_GROUP = app.config['LDAP_ADMIN_GROUP'] -LDAP_USER_GROUP = app.config['LDAP_USER_GROUP'] +if LDAP_GROUP_SECURITY == True: + LDAP_ADMIN_GROUP = app.config['LDAP_ADMIN_GROUP'] + LDAP_USER_GROUP = app.config['LDAP_USER_GROUP'] PDNS_STATS_URL = app.config['PDNS_STATS_URL'] PDNS_API_KEY = app.config['PDNS_API_KEY'] From 5914c3cc867e1be9365e3eeff0dbfb64195f84e2 Mon Sep 17 00:00:00 2001 From: Ivan Filippov Date: Tue, 12 Apr 2016 21:12:51 -0600 Subject: [PATCH 03/11] Add group-based security implementation for non-AD LDAP servers. --- app/models.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/app/models.py b/app/models.py index 811a151..2af7e6a 100644 --- a/app/models.py +++ b/app/models.py @@ -178,11 +178,16 @@ class User(db.Model): l.simple_bind_s(ldap_username, self.password) if LDAP_GROUP_SECURITY: try: - groupSearchFilter = "(&(objectcategory=group)(member=%s))" % ldap_username + if LDAP_TYPE == 'ldap': + uid = result[0][0][1]['uid'][0] + groupSearchFilter = "(&(objectClass=posixGroup)(memberUid=%s))" % uid + else: + groupSearchFilter = "(&(objectcategory=group)(member=%s))" % ldap_username groups = self.ldap_search(groupSearchFilter, LDAP_SEARCH_BASE) allowedlogin = False isadmin = False for group in groups: + logging.debug(group) if (group[0][0] == LDAP_ADMIN_GROUP): allowedlogin = True isadmin = True @@ -194,7 +199,7 @@ class User(db.Model): logging.error('User %s is not part of the "%s" or "%s" groups that allow access to PowerDNS-Admin' % (self.username,LDAP_ADMIN_GROUP,LDAP_USER_GROUP)) return False except: - logging.error('LDAP group lookup for user %s has failed' % self.username) + logging.error('LDAP group lookup for user "%s" has failed' % self.username) logging.info('User "%s" logged in successfully' % self.username) # create user if not exist in the db @@ -227,7 +232,7 @@ class User(db.Model): self.set_admin(isadmin) return True except: - logging.error('User "%s" input a wrong password(stage2)' % self.username) + logging.error('User "%s" input a wrong password' % self.username) return False else: logging.error('Unsupported authentication method') From 03e0f5079507c1177518c2032bd86663a0a992d9 Mon Sep 17 00:00:00 2001 From: ssendev Date: Thu, 18 Aug 2016 22:05:15 +0200 Subject: [PATCH 04/11] Allow to change root domain record via dyndns --- app/views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views.py b/app/views.py index a33e6e1..756c413 100644 --- a/app/views.py +++ b/app/views.py @@ -762,12 +762,12 @@ def dyndns_update(): domain = None domain_segments = hostname.split('.') for index in range(len(domain_segments)): - domain_segments.pop(0) full_domain = '.'.join(domain_segments) potential_domain = Domain.query.filter(Domain.name == full_domain).first() if potential_domain in domains: domain = potential_domain break + domain_segments.pop(0) if not domain: history = History(msg="DynDNS update: attempted update of %s but it does not exist for this user" % hostname, created_by=current_user.username) From f3f9e8d73cf382cf3aff5b2d8da75281554a6aeb Mon Sep 17 00:00:00 2001 From: toxicvengeance Date: Tue, 9 May 2017 21:27:35 +0200 Subject: [PATCH 05/11] Updated config_template.py Added CAA and SRV record to standard template --- config_template.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config_template.py b/config_template.py index 288ff47..8995d15 100644 --- a/config_template.py +++ b/config_template.py @@ -75,7 +75,7 @@ PDNS_API_KEY = 'you never know' PDNS_VERSION = '3.4.7' # RECORDS ALLOWED TO EDIT -RECORDS_ALLOW_EDIT = ['A', 'AAAA', 'CNAME', 'SPF', 'PTR', 'MX', 'TXT'] +RECORDS_ALLOW_EDIT = ['A', 'AAAA', 'CAA', 'CNAME', 'MX', 'PTR', 'SPF', 'SRV', 'TXT'] # EXPERIMENTAL FEATURES PRETTY_IPV6_PTR = False From 85694e4e931ebee9a626c9709d7be8b307d79e73 Mon Sep 17 00:00:00 2001 From: Christopher Himmel Date: Wed, 10 May 2017 22:30:06 +0200 Subject: [PATCH 06/11] added caa record helper --- app/templates/domain.html | 31 ++++++++++++++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/app/templates/domain.html b/app/templates/domain.html index eeb4c27..36d6ae9 100644 --- a/app/templates/domain.html +++ b/app/templates/domain.html @@ -265,7 +265,36 @@ $(document.body).on("focus", "#current_edit_record_data", function (e) { var record_type = $(this).parents("tr").find('#record_type').val(); var record_data = $(this); - if (record_type == "MX") { + if (record_type == "CAA") { + var modal = $("#modal_custom_record"); + if (record_data.val() == "") { + var form = " \ + \ + \ + \ + \ + \ + "; + } else { + var parts = record_data.val().split(" "); + var form = " \ + \ + \ + \ + \ + \ + "; + } + modal.find('.modal-body p').html(form); + modal.find('#button_save').click(function() { + caa_flag = modal.find('#caa_flag').val(); + caa_tag = modal.find('#caa_tag').val(); + caa_value = modal.find('#caa_value').val(); + data = caa_flag + " " + caa_tag + " " + caa_value; + record_data.val(data); + modal.modal('hide'); + }) + } else if (record_type == "MX") { var modal = $("#modal_custom_record"); if (record_data.val() == "") { var form = " \ From 300af228592c9ae38530a1b4cde3486592141059 Mon Sep 17 00:00:00 2001 From: toxicvengeance Date: Wed, 10 May 2017 22:33:44 +0200 Subject: [PATCH 07/11] added caa record helper --- app/templates/domain.html | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/templates/domain.html b/app/templates/domain.html index 36d6ae9..af560a7 100644 --- a/app/templates/domain.html +++ b/app/templates/domain.html @@ -290,10 +290,11 @@ caa_flag = modal.find('#caa_flag').val(); caa_tag = modal.find('#caa_tag').val(); caa_value = modal.find('#caa_value').val(); - data = caa_flag + " " + caa_tag + " " + caa_value; + data = caa_flag + " " + caa_tag + " " + '"' + caa_value + '"'; record_data.val(data); modal.modal('hide'); }) + modal.modal('show'); } else if (record_type == "MX") { var modal = $("#modal_custom_record"); if (record_data.val() == "") { From c9bfe00e59975fb7d85f7898cc936044e028b545 Mon Sep 17 00:00:00 2001 From: toxicvengeance Date: Wed, 10 May 2017 23:15:01 +0200 Subject: [PATCH 08/11] added example caa values --- app/templates/domain.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/templates/domain.html b/app/templates/domain.html index af560a7..ded785b 100644 --- a/app/templates/domain.html +++ b/app/templates/domain.html @@ -280,9 +280,9 @@ var form = " \ \ \ - \ + \ \ - \ + \ "; } modal.find('.modal-body p').html(form); From 5c5beec2d6993c7174e723e758453b6245498334 Mon Sep 17 00:00:00 2001 From: toxicvengeance Date: Wed, 10 May 2017 23:25:32 +0200 Subject: [PATCH 09/11] added default values --- app/templates/domain.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/templates/domain.html b/app/templates/domain.html index ded785b..af6b30e 100644 --- a/app/templates/domain.html +++ b/app/templates/domain.html @@ -271,9 +271,9 @@ var form = " \ \ \ - \ + \ \ - \ + \ "; } else { var parts = record_data.val().split(" "); From 6a47b1e4756636ef983db9b595984ba77ed3b9fc Mon Sep 17 00:00:00 2001 From: Thomas Date: Thu, 2 Nov 2017 02:41:26 +0100 Subject: [PATCH 10/11] added travis status --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 7d402fe..88d8ae4 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,6 @@ # PowerDNS-Admin PowerDNS Web-GUI - Built by Flask +[![Build Status](https://travis-ci.org/thomasDOTde/PowerDNS-Admin.svg?branch=master)](https://travis-ci.org/thomasDOTde/PowerDNS-Admin) #### Features: - Multiple domain management From 9e719a3a9888c0cc2addd1d85287372656f86f26 Mon Sep 17 00:00:00 2001 From: thomasDOTde Date: Fri, 3 Nov 2017 00:00:04 +0100 Subject: [PATCH 11/11] fixed merge --- app/models.py | 22 ---------------------- 1 file changed, 22 deletions(-) diff --git a/app/models.py b/app/models.py index 7514854..a7f9872 100644 --- a/app/models.py +++ b/app/models.py @@ -288,28 +288,6 @@ class User(db.Model): else: logging.error('Unsupported authentication method') return False - # try to get user's firstname & lastname from LDAP - # this might be changed in the future - self.firstname = result[0][0][1]['givenName'][0] - self.lastname = result[0][0][1]['sn'][0] - self.email = result[0][0][1]['mail'][0] - except Exception: - self.firstname = self.username - self.lastname = '' - - # first register user will be in Administrator role - self.role_id = Role.query.filter_by(name='User').first().id - if User.query.count() == 0: - self.role_id = Role.query.filter_by(name='Administrator').first().id - - self.create_user() - logging.info('Created user "%s" in the DB' % self.username) - - return True - - logging.error('Unsupported authentication method') - return False - def create_user(self): """