From 6a5bef5e6a91effb4f7a707e131118121acb863b Mon Sep 17 00:00:00 2001 From: dimpapac Date: Wed, 8 Sep 2021 15:02:49 +0300 Subject: [PATCH 1/7] oidc Roles provisioning --- powerdnsadmin/models/setting.py | 4 + powerdnsadmin/models/user.py | 14 ++- powerdnsadmin/routes/admin.py | 19 +++ powerdnsadmin/routes/index.py | 28 ++++- .../admin_setting_authentication.html | 111 ++++++++++++++++++ 5 files changed, 170 insertions(+), 6 deletions(-) diff --git a/powerdnsadmin/models/setting.py b/powerdnsadmin/models/setting.py index 429ce1e..ad86bff 100644 --- a/powerdnsadmin/models/setting.py +++ b/powerdnsadmin/models/setting.py @@ -43,6 +43,10 @@ class Setting(db.Model): 'urn_value':'', 'autoprovisioning_attribute': '', 'purge': False, + 'autoprovisioning_oidc': False, + 'urn_value_oidc':'', + 'autoprovisioning_attribute_oidc': '', + 'purge_oidc': False, 'verify_user_email': False, 'ldap_enabled': False, 'ldap_type': 'ldap', diff --git a/powerdnsadmin/models/user.py b/powerdnsadmin/models/user.py index 72491a3..9b3a8d1 100644 --- a/powerdnsadmin/models/user.py +++ b/powerdnsadmin/models/user.py @@ -658,11 +658,14 @@ class User(db.Model): current_app.logger.warning("Cannot apply autoprovisioning on user: {}".format(e)) return entitlements - def updateUser(self, Entitlements): + def updateUser(self, Entitlements, oidc=False): """ Update user associations based on ldap attribute """ - entitlements= getCorrectEntitlements(Entitlements) + if oidc==False: + entitlements = getCorrectEntitlements(Entitlements) + else: + entitlements = getCorrectEntitlements(Entitlements, oidc) if len(entitlements)!=0: self.revoke_privilege(True) for entitlement in entitlements: @@ -701,12 +704,15 @@ class User(db.Model): if account!=None: account.add_user(user) -def getCorrectEntitlements(Entitlements): +def getCorrectEntitlements(Entitlements, oidc=False): """ Gather a list of valid records from the ldap attribute given """ from ..models.role import Role - urn_value=Setting().get('urn_value') + if (oidc == False): + urn_value=Setting().get('urn_value') + else: + urn_value=Setting().get('urn_value_oidc') urnArgs=[x.lower() for x in urn_value.split(':')] entitlements=[] for Entitlement in Entitlements: diff --git a/powerdnsadmin/routes/admin.py b/powerdnsadmin/routes/admin.py index 8e119a2..89912b5 100644 --- a/powerdnsadmin/routes/admin.py +++ b/powerdnsadmin/routes/admin.py @@ -998,6 +998,25 @@ def setting_authentication(): request.form.get('oidc_oauth_account_name_property')) Setting().set('oidc_oauth_account_description_property', request.form.get('oidc_oauth_account_description_property')) + Setting().set('autoprovisioning_oidc', True + if request.form.get('autoprovisioning_oidc') == 'ON' else False) + Setting().set('autoprovisioning_attribute_oidc', + request.form.get('autoprovisioning_attribute_oidc')) + + if request.form.get('autoprovisioning_oidc')=='ON': + if validateURN(request.form.get('urn_value_oidc')): + Setting().set('urn_value_oidc', + request.form.get('urn_value_oidc')) + else: + return render_template('admin_setting_authentication.html', + error="Invalid urn") + else: + Setting().set('urn_value_oidc', + request.form.get('urn_value_oidc')) + + Setting().set('purge_oidc', True + if request.form.get('purge_oidc') == 'ON' else False) + result = { 'status': True, 'msg': diff --git a/powerdnsadmin/routes/index.py b/powerdnsadmin/routes/index.py index f646524..9086a87 100644 --- a/powerdnsadmin/routes/index.py +++ b/powerdnsadmin/routes/index.py @@ -373,7 +373,8 @@ def login(): return redirect(url_for('index.index')) if 'oidc_token' in session: - me = json.loads(oidc.get('userinfo').text) + me = json.loads(oidc.get('profile').text) + # me = json.loads(oidc.get('userinfo').text) oidc_username = me[Setting().get('oidc_oauth_username')] oidc_givenname = me[Setting().get('oidc_oauth_firstname')] oidc_familyname = me[Setting().get('oidc_oauth_last_name')] @@ -398,7 +399,7 @@ def login(): session.pop('oidc_token', None) return redirect(url_for('index.login')) - if Setting().get('oidc_oauth_account_name_property') and Setting().get('oidc_oauth_account_description_property'): + if Setting().get('oidc_oauth_account_name_property') and Setting().get('oidc_oauth_account_description_property') and not Setting().get('autoprovisioning_oidc'): name_prop = Setting().get('oidc_oauth_account_name_property') desc_prop = Setting().get('oidc_oauth_account_description_property') if name_prop in me and desc_prop in me: @@ -409,6 +410,28 @@ def login(): if ua.name != account.name: ua.remove_user(user) + if Setting().get('autoprovisioning_oidc'): + Entitlements=[] + urn_value=Setting().get('urn_value_oidc') + key=Setting().get('autoprovisioning_attribute_oidc') + Entitlements=me['attributes'][key] + #Entitlements=user.read_entitlements_oidc(Setting().get('autoprovisioning_attribute_oidc'), me) + if len(Entitlements)==0 and Setting().get('purge_oidc'): + user.set_role("User") + user.revoke_privilege(True) + elif len(Entitlements)!=0: + if isinstance(Entitlements, str): + Entitlements=[Entitlements] + if checkForPDAEntries(Entitlements, urn_value): + user.updateUser(Entitlements, True) + else: + current_app.logger.warning('Not a single powerdns-admin record was found, possibly a typo in the prefix') + if Setting().get('purge_oidc'): + user.set_role("User") + user.revoke_privilege(True) + current_app.logger.warning('Procceding to revoke every privilige from ' + user.username + '.' ) + + session['user_id'] = user.id session['authentication_type'] = 'OAuth' login_user(user, remember=False) @@ -498,6 +521,7 @@ def checkForPDAEntries(Entitlements, urn_value): """ Run through every record located in the ldap attribute given and determine if there are any valid powerdns-admin records """ + urnArguments=[x.lower() for x in urn_value.split(':')] for Entitlement in Entitlements: entArguments=Entitlement.split(':powerdns-admin') diff --git a/powerdnsadmin/templates/admin_setting_authentication.html b/powerdnsadmin/templates/admin_setting_authentication.html index 640c5ca..8146987 100644 --- a/powerdnsadmin/templates/admin_setting_authentication.html +++ b/powerdnsadmin/templates/admin_setting_authentication.html @@ -655,6 +655,42 @@
ADVANCE +
+ +
+ +     +
+
+
+ + + +
+
+ + + {% if error %} + Please input the correct prefix for your urn value + {% endif %} +
+
+ +
+ +     +
+
@@ -1022,6 +1058,12 @@ $('#oidc_oauth_firstname').prop('required', true); $('#oidc_oauth_last_name').prop('required', true); $('#oidc_oauth_email').prop('required', true); + + if ($('#autoprovisioning_oidc').is(":checked")) { + $('#autoprovisioning_attribute_oidc').prop('required', true); + $('#urn_value_oidc').prop('required', true); + } + } else { $('#oidc_oauth_key').prop('required', false); $('#oidc_oauth_secret').prop('required', false); @@ -1033,8 +1075,27 @@ $('#oidc_oauth_firstname').prop('required', false); $('#oidc_oauth_last_name').prop('required', false); $('#oidc_oauth_email').prop('required', false); + + if ($('#autoprovisioning_oidc').is(":checked")) { + $('#autoprovisioning_attribute_oidc').prop('required', false); + $('#urn_value_oidc').prop('required', true); + } } }); + + $("input[name='autoprovisioning_oidc']" ).change(function(){ + if ($('#autoprovisioning_oidc_on').is(":checked") && $('#oidc_oauth_enabled').is(":checked")) { + $('#autoprovisioning_attribute_oidc').prop('required', true); + $('#urn_value_oidc').prop('required', true); + $('#purge_oidc').prop('required', true); + } + else{ + $('#autoprovisioning_attribute_oidc').prop('required', false); + $('#urn_value_oidc').prop('required', false); + $('#purge_oidc').prop('required', false); + } + }); + // init validation requirement at first time page load {% if SETTING.get('oidc_oauth_enabled') %} $('#oidc_oauth_key').prop('required', true); @@ -1047,7 +1108,35 @@ $('#oidc_oauth_firstname').prop('required', true); $('#oidc_oauth_last_name').prop('required', true); $('#oidc_oauth_email').prop('required', true); + + if ($('#autoprovisioning_oidc_on').is(":checked")) { + $('#autoprovisioning_attribute_oidc').prop('required', true); + $('#urn_value_oidc').prop('required', true); + } {% endif %} + + $("input[name='purge_oidc']" ).change(function(){ + if ($("#purge_oidc_on").is(":checked")){ + document.getElementById('purge_oidc_on').checked=false; + document.getElementById('purge_oidc_off').checked=true; + var modal = $("#modal_confirm_oidc"); + var info = "Are you sure you want to do this? Users will lose their associated domains unless they already have their autoprovisioning field prepopulated." ; + modal.find('.modal-body p').text(info); + modal.find('#button_confirm').click(function () { + document.getElementById('purge_oidc_on').checked=true; + document.getElementById('purge_oidc_off').checked=false; + modal.modal('hide'); + }) + modal.find('#button_cancel').click(function () { + modal.modal('hide'); + }) + modal.find('#X').click(function () { + modal.modal('hide'); + }) + modal.modal('show'); + } + }); + //END: OIDC Tab JS @@ -1077,6 +1166,28 @@
+ +
ADVANCE +
+ + + +
+
+ + + +
+
@@ -691,16 +702,6 @@ ON
-
- - - -
-
- - - -
@@ -1062,6 +1063,8 @@ if ($('#autoprovisioning_oidc').is(":checked")) { $('#autoprovisioning_attribute_oidc').prop('required', true); $('#urn_value_oidc').prop('required', true); + $('#oidc_oauth_account_name_property').prop('readonly', true); + $('#oidc_oauth_account_description_property').prop('readonly', true); } } else { @@ -1079,11 +1082,31 @@ if ($('#autoprovisioning_oidc').is(":checked")) { $('#autoprovisioning_attribute_oidc').prop('required', false); $('#urn_value_oidc').prop('required', true); + $('#oidc_oauth_account_name_property').prop('readonly', true); + $('#oidc_oauth_account_description_property').prop('readonly', true); } } }); + var handler= function(event, variable){ + if( variable.is('[readonly]') ){ + variable.click( function () { + var modal = $("#modal_warning"); + var info = "Roles autoprovisioning is enabled, to use this feature disable it first." ; + modal.find('.modal-body p').text(info); + modal.find('#button_warning_confirm').click(function () { + modal.modal('hide'); + }) + modal.find('#warning_X').click(function () { + modal.modal('hide'); + }) + modal.modal('show'); + }); + } + } + $("input[name='autoprovisioning_oidc']" ).change(function(){ + if ($('#autoprovisioning_oidc_on').is(":checked") && $('#oidc_oauth_enabled').is(":checked")) { $('#autoprovisioning_attribute_oidc').prop('required', true); $('#urn_value_oidc').prop('required', true); @@ -1093,11 +1116,48 @@ $('#autoprovisioning_attribute_oidc').prop('required', false); $('#urn_value_oidc').prop('required', false); $('#purge_oidc').prop('required', false); + $('#oidc_oauth_account_name_property').prop('readonly', false); + $('#oidc_oauth_account_description_property').prop('readonly', false); + $('#oidc_oauth_account_name_property').mouseover( function () { + $(this).css("cursor", "text") + }); + $('#oidc_oauth_account_description_property').mouseover( function () { + $(this).css("cursor", "text") + }); + $('#oidc_oauth_account_name_property').off("click"); + $('#oidc_oauth_account_description_property').off("click"); + } + + if ($('#autoprovisioning_oidc_on').is(":checked")){ + document.getElementById('autoprovisioning_oidc_on').checked=false; + document.getElementById('autoprovisioning_oidc_off').checked=true; + var modal= $("#modal_confirm_oidc"); + var info = "Are you sure you want to do this? By enabling Roles autoprovisioning you are disabling the feature above" ; + modal.find('.modal-body p').text(info); + modal.find('#button_confirm').click(function () { + document.getElementById('autoprovisioning_oidc_on').checked=true; + document.getElementById('autoprovisioning_oidc_off').checked=false; + $('#oidc_oauth_account_name_property').prop('readonly', true); + $('#oidc_oauth_account_description_property').prop('readonly', true); + $('#oidc_oauth_account_name_property').mouseover( function () { + $(this).css("cursor", "not-allowed") + }); + $('#oidc_oauth_account_description_property').mouseover( function () { + $(this).css("cursor", "not-allowed") + }); + modal.modal('hide'); + }) + modal.find('#button_cancel').click(function () { + modal.modal('hide'); + }) + modal.find('#X').click(function () { + modal.modal('hide'); + }) + modal.modal('show'); } }); - // init validation requirement at first time page load - {% if SETTING.get('oidc_oauth_enabled') %} + {% if SETTING.get('oidc_oauth_enabled') %} $('#oidc_oauth_key').prop('required', true); $('#oidc_oauth_secret').prop('required', true); $('#oidc_oauth_scope').prop('required', true); @@ -1112,6 +1172,16 @@ if ($('#autoprovisioning_oidc_on').is(":checked")) { $('#autoprovisioning_attribute_oidc').prop('required', true); $('#urn_value_oidc').prop('required', true); + $('#oidc_oauth_account_name_property').prop('readonly', true); + $('#oidc_oauth_account_description_property').prop('readonly', true); + $('#oidc_oauth_account_name_property').mouseover( function () { + $(this).css("cursor", "not-allowed") + }); + $('#oidc_oauth_account_description_property').mouseover( function () { + $(this).css("cursor", "not-allowed") + }); + $('#oidc_oauth_account_name_property').on("click", handler(event, $('#oidc_oauth_account_name_property'))); + $('#oidc_oauth_account_description_property').on("click", handler(event, $('#oidc_oauth_account_description_property'))); } {% endif %} @@ -1136,7 +1206,6 @@ modal.modal('show'); } }); - //END: OIDC Tab JS From 5cd2b30d0a98b4c61adcbf615d5cfe48b08ea55a Mon Sep 17 00:00:00 2001 From: KostasMparmparousis Date: Mon, 13 Sep 2021 10:57:59 +0300 Subject: [PATCH 3/7] oidc role provisioning small fix --- powerdnsadmin/models/user.py | 13 +++---------- powerdnsadmin/routes/index.py | 5 ++--- 2 files changed, 5 insertions(+), 13 deletions(-) diff --git a/powerdnsadmin/models/user.py b/powerdnsadmin/models/user.py index 9b3a8d1..7ea6227 100644 --- a/powerdnsadmin/models/user.py +++ b/powerdnsadmin/models/user.py @@ -658,14 +658,11 @@ class User(db.Model): current_app.logger.warning("Cannot apply autoprovisioning on user: {}".format(e)) return entitlements - def updateUser(self, Entitlements, oidc=False): + def updateUser(self, Entitlements, urn_value): """ Update user associations based on ldap attribute """ - if oidc==False: - entitlements = getCorrectEntitlements(Entitlements) - else: - entitlements = getCorrectEntitlements(Entitlements, oidc) + entitlements = getCorrectEntitlements(Entitlements, urn_value) if len(entitlements)!=0: self.revoke_privilege(True) for entitlement in entitlements: @@ -704,15 +701,11 @@ class User(db.Model): if account!=None: account.add_user(user) -def getCorrectEntitlements(Entitlements, oidc=False): +def getCorrectEntitlements(Entitlements, urn_value): """ Gather a list of valid records from the ldap attribute given """ from ..models.role import Role - if (oidc == False): - urn_value=Setting().get('urn_value') - else: - urn_value=Setting().get('urn_value_oidc') urnArgs=[x.lower() for x in urn_value.split(':')] entitlements=[] for Entitlement in Entitlements: diff --git a/powerdnsadmin/routes/index.py b/powerdnsadmin/routes/index.py index 1598b7c..22ade73 100644 --- a/powerdnsadmin/routes/index.py +++ b/powerdnsadmin/routes/index.py @@ -419,7 +419,7 @@ def login(): user.revoke_privilege(True) elif len(Entitlements)!=0: if checkForPDAEntries(Entitlements, urn_value): - user.updateUser(Entitlements, True) + user.updateUser(Entitlements, urn_value) else: current_app.logger.warning('Not a single powerdns-admin record was found, possibly a typo in the prefix') if Setting().get('purge_oidc'): @@ -500,7 +500,7 @@ def login(): elif len(Entitlements)!=0: if checkForPDAEntries(Entitlements, urn_value): - user.updateUser(Entitlements) + user.updateUser(Entitlements, urn_value) else: current_app.logger.warning('Not a single powerdns-admin record was found, possibly a typo in the prefix') if Setting().get('purge'): @@ -516,7 +516,6 @@ def checkForPDAEntries(Entitlements, urn_value): """ Run through every record located in the ldap attribute given and determine if there are any valid powerdns-admin records """ - urnArguments=[x.lower() for x in urn_value.split(':')] for Entitlement in Entitlements: entArguments=Entitlement.split(':powerdns-admin') From 44dd602e414e5bbd467058e7583c2587f68df676 Mon Sep 17 00:00:00 2001 From: KostasMparmparousis Date: Mon, 13 Sep 2021 13:07:57 +0300 Subject: [PATCH 4/7] oidc role provisioning docs addition --- docs/Autoprovision.md | 43 +++++++++++++ .../admin_setting_authentication.html | 62 ++++++++++++++++--- 2 files changed, 98 insertions(+), 7 deletions(-) create mode 100644 docs/Autoprovision.md diff --git a/docs/Autoprovision.md b/docs/Autoprovision.md new file mode 100644 index 0000000..8a74af9 --- /dev/null +++ b/docs/Autoprovision.md @@ -0,0 +1,43 @@ +Larger organisations often have many active applications such as PowerDns-Admin. Therefore, occasionally the need arises for there to be an access management mechanism that monitors every application available, especially concerning the role management aspect on each application. + +Powerdns-admin already provides a mechanism for a finer grained control of users' privileges by using LDAP static groups, which is configurable with the set of options under the "Group Security". However, while static groups are widely known and available on the majority of the ldap deployments, nowadays are considered administratively inflexible. A workaround to static groups limitations is offered by more recent versions of OpenLDAP servers(openldap 2.5.x) via dynamic groups. + +However recent designs tend to implement access management based on ldap attributes with URN syntax values that reside on the user's object and encompass into their structure all service specific membership scenarios, otherwise referred to as autoprovisioning using URN values. +With our proposed feature, we do implement autoprovisioning using URN values utilizing an attribute passed in the user's LDAP Object. The allowed syntax for records inside this attribute is: + +```text +if PDA-Role∈[Administrator, Operator]: + syntax:=prefix:"powerdns-admin":PDA-Role +else: + syntax:=prefix:"powerdns-admin":PDA-Role:: + +where prefix is given by an admin of PDA in the configurable field "ADVANCE:Urn prefix". + +i.e. some valid urn values could be: +urn:yourNID:yourOrganization:powerdns-admin:Administrator +urn:yourNID:yourOrganization:powerdns-admin:User:example.com (supposing there is a domain in the local db called "example.com") +urn:yourNID:yourOrganization:powerdns-admin:User:example.com:examplenet (supposing there is an account in the local db called "examplenet") +urn:yourNID:yourOrganization:powerdns-admin:User::examplenet +``` + +In order to keep users' privileges in-sync between the PDA's database and the ldap, when no valid "powerdns-admin" values are found for the logged-in user, PDA will purge all privileges from the local database for this user. To avoid unintentional wipe outs of existing PDA privileges especially when admins enable this feature for the first time, also available in the proposed feature is the option "Purge Roles if empty". If toggled on, ldap entries that have no valid "powerdns-admin" records to their object's attribute, will lose all their associations with any domain or account, also reverting to a PDA-User in the process, despite their current role in the local db. If toggled off, in the same scenario they get to keep their existing associations and their current PDA-Role. + +How to configure: +1) Login as an admin to PowerDNS Admin. +2) Go to Settings --> Authentication. +3) Under Authentication, select LDAP. +4) Disable Group Security, if enabled. +5) Click the Radio Button for Role Autoprovisioning. +6) Fill in the required info - + +* Role Provisioning field - your_LDAP_Field. +* Urn prefix - your_URN_Prefix. + +7) Enable Purge Roles If Empty, if you so wish, and click confirm when the prompt appears. +8) Click Save. + +image + + +Last but not least, provisioning PDA user privileges using urn values is a feature that can also be achieved while deploying other release mechanisms that PowerDns-Admin already supports, such as OpenID Connect. For these authentication providers there will occur a patch later down the line implementing the proposed feature to their standards/specifications. + diff --git a/powerdnsadmin/templates/admin_setting_authentication.html b/powerdnsadmin/templates/admin_setting_authentication.html index 365dc40..7711a16 100644 --- a/powerdnsadmin/templates/admin_setting_authentication.html +++ b/powerdnsadmin/templates/admin_setting_authentication.html @@ -310,19 +310,33 @@
ADVANCE
-
Provision PDA user privileges based on LDAP Object Attributes. Alternative to Group Security Role Management. +
Provision PDA user privileges based on LDAP Object Attributes.
  • Roles Autoprovisioning - If toggled on, the PDA Role and the associations of users found in the local db, will be instantly updated from the LDAP server every time they log in.
  • - Roles provisioning field - The attribute in the ldap server populated by the urn values where PDA will look for a new Role and/or new associations to domains/accounts. + Roles provisioning field - The attribute in the LDAP server populated by the urn values where PDA will look for a new Role and/or new associations to domains/accounts. The allowed syntax for records inside this attribute in your LDAP server is:
    + if PDA-Role∈[Administrator, Operator]: +
      +
    • + syntax:=prefix:"powerdns-admin":PDA-Role +
    • +
    + else:
    +
      +
    • + syntax:=prefix:"powerdns-admin":PDA-Role:<domain>:<account> +
    • +
    + where prefix is given in the field "Urn prefix". +
  • - Urn prefix - The prefix used before the static keyword "powerdns-admin" for your entitlements in the ldap server. Must comply with RFC no.8141. + Urn prefix - The prefix used before the static keyword "powerdns-admin" for your entitlements in the LDAP server. Must comply with RFC no.8141.
  • - Purge Roles If Empty - If toggled on, ldap entries that have no valid "powerdns-admin" records to their autoprovisioning field, will lose all their associations with any domain or account, also reverting to a User in the process, despite their current role in the local db.
    If toggled off, in the same scenario they get to keep their existing associations and their current Role. + Purge Roles If Empty - If toggled on, LDAP entries that have no valid "powerdns-admin" records to their autoprovisioning field, will lose all their associations with any domain or account, also reverting to a User in the process, despite their current role in the local db.
    If toggled off, in the same scenario they get to keep their existing associations and their current Role.
@@ -710,7 +724,41 @@
Help -

Fill in all the fields in the left form.

+
+
ADVANCE
+

Fill in all the fields in the left form.

+
Provision PDA user privileges based on OIDC Object Attributes. +
    +
  • + Roles Autoprovisioning - If toggled on, the PDA Role and the associations of users found in the local db, will be instantly updated from the OIDC server every time they log in. +
  • +
  • + Roles provisioning field - The attribute in the OIDC server populated by the urn values where PDA will look for a new Role and/or new associations to domains/accounts. The allowed syntax for records inside this attribute in your OIDC server is:
    + if PDA-Role∈[Administrator, Operator]: +
      +
    • + syntax:=prefix:"powerdns-admin":PDA-Role +
    • +
    + else:
    +
      +
    • + syntax:=prefix:"powerdns-admin":PDA-Role:<domain>:<account> +
    • +
    + where prefix is given in the field "Urn prefix". +
    +
  • +
  • + Urn prefix - The prefix used before the static keyword "powerdns-admin" for your entitlements in the OIDC server. Must comply with RFC no.8141. +
  • +
  • + Purge Roles If Empty - If toggled on, OIDC entries that have no valid "powerdns-admin" records to their autoprovisioning field, will lose all their associations with any domain or account, also reverting to a User in the process, despite their current role in the local db.
    If toggled off, in the same scenario they get to keep their existing associations and their current Role. + +
  • +
+
+
@@ -1092,7 +1140,7 @@ if( variable.is('[readonly]') ){ variable.click( function () { var modal = $("#modal_warning"); - var info = "Roles autoprovisioning is enabled, to use this feature disable it first." ; + var info = "Roles Autoprovisioning is enabled, to use this feature disable it first." ; modal.find('.modal-body p').text(info); modal.find('#button_warning_confirm').click(function () { modal.modal('hide'); @@ -1132,7 +1180,7 @@ document.getElementById('autoprovisioning_oidc_on').checked=false; document.getElementById('autoprovisioning_oidc_off').checked=true; var modal= $("#modal_confirm_oidc"); - var info = "Are you sure you want to do this? By enabling Roles autoprovisioning you are disabling the feature above" ; + var info = "Are you sure you want to do this? By enabling Roles Autoprovisioning you are disabling the feature above" ; modal.find('.modal-body p').text(info); modal.find('#button_confirm').click(function () { document.getElementById('autoprovisioning_oidc_on').checked=true; From 09011ee490bcc167f8c0fb46dec3c0f9b6b25187 Mon Sep 17 00:00:00 2001 From: Dimitris Papachristou <35890684+dimpapac@users.noreply.github.com> Date: Mon, 13 Sep 2021 14:23:28 +0300 Subject: [PATCH 5/7] Update Autoprovision.md --- docs/Autoprovision.md | 35 +++++++++++++++++++++++------------ 1 file changed, 23 insertions(+), 12 deletions(-) diff --git a/docs/Autoprovision.md b/docs/Autoprovision.md index 8a74af9..cb82d1d 100644 --- a/docs/Autoprovision.md +++ b/docs/Autoprovision.md @@ -1,11 +1,8 @@ -Larger organisations often have many active applications such as PowerDns-Admin. Therefore, occasionally the need arises for there to be an access management mechanism that monitors every application available, especially concerning the role management aspect on each application. +Provisioning the roles and the associations of a user based on an attribute in his object, is a very useful practice for a variety of reasons, and can be implemented across multiple authentication providers for PDA. Below we demonstrate how to enable and configure Roles&Associations Provisioning during LDAP and OIDC authentication. -Powerdns-admin already provides a mechanism for a finer grained control of users' privileges by using LDAP static groups, which is configurable with the set of options under the "Group Security". However, while static groups are widely known and available on the majority of the ldap deployments, nowadays are considered administratively inflexible. A workaround to static groups limitations is offered by more recent versions of OpenLDAP servers(openldap 2.5.x) via dynamic groups. +The allowed syntax for records inside the attribute of the user's object is: -However recent designs tend to implement access management based on ldap attributes with URN syntax values that reside on the user's object and encompass into their structure all service specific membership scenarios, otherwise referred to as autoprovisioning using URN values. -With our proposed feature, we do implement autoprovisioning using URN values utilizing an attribute passed in the user's LDAP Object. The allowed syntax for records inside this attribute is: - -```text +```text. if PDA-Role∈[Administrator, Operator]: syntax:=prefix:"powerdns-admin":PDA-Role else: @@ -19,16 +16,17 @@ urn:yourNID:yourOrganization:powerdns-admin:User:example.com (supposi urn:yourNID:yourOrganization:powerdns-admin:User:example.com:examplenet (supposing there is an account in the local db called "examplenet") urn:yourNID:yourOrganization:powerdns-admin:User::examplenet ``` +Note: To use Roles&Associations Provisioning in it's fullest potential, the domains and the accounts provided in the entries must already exist, or else entries with no match in the local db will be skipped. -In order to keep users' privileges in-sync between the PDA's database and the ldap, when no valid "powerdns-admin" values are found for the logged-in user, PDA will purge all privileges from the local database for this user. To avoid unintentional wipe outs of existing PDA privileges especially when admins enable this feature for the first time, also available in the proposed feature is the option "Purge Roles if empty". If toggled on, ldap entries that have no valid "powerdns-admin" records to their object's attribute, will lose all their associations with any domain or account, also reverting to a PDA-User in the process, despite their current role in the local db. If toggled off, in the same scenario they get to keep their existing associations and their current PDA-Role. +In order to keep users' privileges in-sync between the PDA's database and the LDAP or the OIDC, when no valid "powerdns-admin" values are found for the logged-in user, PDA will purge all privileges from the local database for this user. To avoid unintentional wipe outs of existing PDA privileges especially when admins enable this feature for the first time, the option "Purge Roles if empty" is also available. If toggled on, LDAP/OIDC entries that have no valid "powerdns-admin" records to their object's attribute, will lose all their associations with any domain or account, also reverting to a PDA-User in the process, despite their current role in the local db. If toggled off, in the same scenario they get to keep their existing associations and their current PDA-Role. -How to configure: +How to configure LDAP Roles Autoprovisioning: 1) Login as an admin to PowerDNS Admin. 2) Go to Settings --> Authentication. 3) Under Authentication, select LDAP. 4) Disable Group Security, if enabled. -5) Click the Radio Button for Role Autoprovisioning. -6) Fill in the required info - +5) Click the Radio Button for Roles Autoprovisioning. +6) Fill in the required info: * Role Provisioning field - your_LDAP_Field. * Urn prefix - your_URN_Prefix. @@ -36,8 +34,21 @@ How to configure: 7) Enable Purge Roles If Empty, if you so wish, and click confirm when the prompt appears. 8) Click Save. -image +Screenshot-2021-09-13-at-13-39-33-Authentication-Settings-Power-DNS-Admin +How to configure OIDC Roles Autoprovisioning: +1) Login as an admin to PowerDNS Admin. +2) Go to Settings --> Authentication. +3) Under Authentication, select OpenID Connect OAuth. +4) Click the Radio Button for Roles Autoprovisioning. +5) If "Autoprovision Account Name property" and "Autoprovision Account Description property" fields are filled, you will be warned that both features can not be enabled at the same time. This means that if Roles Autoprovisioning is enabled, the other feature is automatically disabled and vice versa. -Last but not least, provisioning PDA user privileges using urn values is a feature that can also be achieved while deploying other release mechanisms that PowerDns-Admin already supports, such as OpenID Connect. For these authentication providers there will occur a patch later down the line implementing the proposed feature to their standards/specifications. +6) Fill in the required info: +* Role Provisioning field - your_OIDC_Field. +* Urn prefix - your_URN_Prefix. + +7) Enable Purge Roles If Empty, if you so wish, and click confirm when the prompt appears. +8) Click Save. + +Screenshot-2021-09-13-at-14-19-42-Authentication-Settings-Power-DNS-Admin From 82f3303b6072e090caeb2ab064e970bc99758505 Mon Sep 17 00:00:00 2001 From: dimpapac Date: Mon, 13 Sep 2021 17:24:03 +0300 Subject: [PATCH 6/7] oidc roles provisioning minor docs addition --- powerdnsadmin/templates/admin_setting_authentication.html | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/powerdnsadmin/templates/admin_setting_authentication.html b/powerdnsadmin/templates/admin_setting_authentication.html index 7711a16..5e92db0 100644 --- a/powerdnsadmin/templates/admin_setting_authentication.html +++ b/powerdnsadmin/templates/admin_setting_authentication.html @@ -310,7 +310,7 @@
ADVANCE
-
Provision PDA user privileges based on LDAP Object Attributes. +
Provision PDA user privileges based on LDAP Object Attributes. This feature and GROUP SECURITY are mutually exclusive.
  • Roles Autoprovisioning - If toggled on, the PDA Role and the associations of users found in the local db, will be instantly updated from the LDAP server every time they log in. @@ -725,9 +725,9 @@
    Help
    -
    ADVANCE

    Fill in all the fields in the left form.

    -
    Provision PDA user privileges based on OIDC Object Attributes. +
    ADVANCE
    +
    Provision PDA user privileges based on OIDC Object Attributes. This feature and "Autoprovision Account Name/Description property" are mutually exclusive.
    • Roles Autoprovisioning - If toggled on, the PDA Role and the associations of users found in the local db, will be instantly updated from the OIDC server every time they log in. From 301532921fce581028b3e17b56232d19f4f61edd Mon Sep 17 00:00:00 2001 From: KostasMparmparousis Date: Tue, 21 Dec 2021 15:27:01 +0200 Subject: [PATCH 7/7] OIDC Provisioning update --- powerdnsadmin/models/user.py | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/powerdnsadmin/models/user.py b/powerdnsadmin/models/user.py index 80a79e8..ffc9a07 100644 --- a/powerdnsadmin/models/user.py +++ b/powerdnsadmin/models/user.py @@ -605,7 +605,7 @@ class User(db.Model): return False def set_role(self, role_name): - role = Role.query.filter(Role.name == role_name).first() + role = Role.query.filter(Role.name == role_name.capitalize()).first() if role: user = User.query.filter(User.username == self.username).first() user.role_id = role.id @@ -676,12 +676,12 @@ class User(db.Model): entitlements = getCorrectEntitlements(Entitlements, urn_value) if len(entitlements)!=0: self.revoke_privilege(True) + role="user" for entitlement in entitlements: arguments=entitlement.split(':') entArgs=arguments[arguments.index('powerdns-admin')+1:] - role= entArgs[0] - self.set_role(role) - if (role=="User") and len(entArgs)>1: + role= self.get_role(role,entArgs[0].lower()) + if (role=="user") and len(entArgs)>1: current_domains=getUserInfo(self.get_user_domains()) current_accounts=getUserInfo(self.get_accounts()) domain=entArgs[1] @@ -689,6 +689,14 @@ class User(db.Model): if len(entArgs)>2: account=entArgs[2] self.addMissingAccount(account, current_accounts) + self.set_role(role) + + def get_role(self, previousRole, newRole): + dict = { "user": 1, "operator" : 2, "administrator" : 3} + if (dict[newRole] > dict[previousRole]): + return newRole + else: + return previousRole def addMissingDomain(self, autoprovision_domain, current_domains): """ @@ -741,7 +749,7 @@ def getCorrectEntitlements(Entitlements, urn_value): continue entArgs=arguments[arguments.index('powerdns-admin')+1:] - role=entArgs[0] + role=entArgs[0].lower() roles= Role.query.all() role_names=get_role_names(roles) @@ -751,7 +759,7 @@ def getCorrectEntitlements(Entitlements, urn_value): continue if len(entArgs)>1: - if (role!="User"): + if (role!="user"): e="Too many arguments for Admin or Operator" current_app.logger.warning("Cannot apply autoprovisioning on user: {}".format(e)) continue @@ -796,7 +804,7 @@ def get_role_names(roles): """ roles_list=[] for role in roles: - roles_list.append(role.name) + roles_list.append(role.name.lower()) return roles_list def getUserInfo(DomainsOrAccounts):