From d223eba0a13f4ef593372f05f49a013e241edaf8 Mon Sep 17 00:00:00 2001 From: vmarkop Date: Fri, 26 Nov 2021 15:58:54 +0200 Subject: [PATCH 01/20] Added UI interface for SAML settings --- powerdnsadmin/models/setting.py | 25 ++ powerdnsadmin/routes/admin.py | 73 +++- .../admin_setting_authentication.html | 350 ++++++++++++++++++ 3 files changed, 447 insertions(+), 1 deletion(-) diff --git a/powerdnsadmin/models/setting.py b/powerdnsadmin/models/setting.py index 2a96349..86bb950 100644 --- a/powerdnsadmin/models/setting.py +++ b/powerdnsadmin/models/setting.py @@ -110,6 +110,31 @@ class Setting(db.Model): 'oidc_oauth_email': 'email', 'oidc_oauth_account_name_property': '', 'oidc_oauth_account_description_property': '', + 'saml_enabled': False, + 'saml_metadata_url': 'https:///FederationMetadata/2007-06/FederationMetadata.xml', + 'saml_metadata_cache_lifetime': '1', + 'saml_idp_sso_binding': 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST', + 'saml_idp_entity_id': 'https://idp.example.edu/idp', + 'saml_nameid_format': 'urn:oid:0.9.2342.19200300.100.1.1', + 'saml_sp_requested_attributes': '[ \ + {"name": "urn:oid:0.9.2342.19200300.100.1.3", "nameFormat": "urn:oasis:names:tc:SAML:2.0:attrname-format:uri", "isRequired": true, "friendlyName": "email"}, \ + {"name": "mail", "isRequired": false, "friendlyName": "test-field"} \ + ]', + 'saml_attribute_email': 'urn:oid:0.9.2342.19200300.100.1.3', + 'saml_attribute_givenname': 'urn:oid:2.5.4.42', + 'saml_attribute_surname': 'urn:oid:2.5.4.4', + 'saml_attribute_username': 'urn:oid:0.9.2342.19200300.100.1.1', + 'saml_attribute_admin': 'https://example.edu/pdns-admin', + 'saml_attribute_account': 'https://example.edu/pdns-account', + 'saml_sp_entity_id': 'http://', + 'saml_sp_entity_name': '', + 'saml_sp_entity_mail': '', + 'saml_cert_file': '/etc/pki/powerdns-admin/cert.crt', + 'saml_cert_key': '/etc/pki/powerdns-admin/key.pem', + 'saml_sign_request': False, + 'saml_logout': False, + 'saml_logout_url': 'https://google.com', + 'saml_assertion_encrypted': True, 'forward_records_allow_edit': { 'A': True, 'AAAA': True, diff --git a/powerdnsadmin/routes/admin.py b/powerdnsadmin/routes/admin.py index 6de8d19..bbe3f4c 100644 --- a/powerdnsadmin/routes/admin.py +++ b/powerdnsadmin/routes/admin.py @@ -753,7 +753,8 @@ def has_an_auth_method(local_db_enabled=None, google_oauth_enabled=None, github_oauth_enabled=None, oidc_oauth_enabled=None, - azure_oauth_enabled=None): + azure_oauth_enabled=None, + saml_enabled=None): if local_db_enabled is None: local_db_enabled = Setting().get('local_db_enabled') if ldap_enabled is None: @@ -766,6 +767,8 @@ def has_an_auth_method(local_db_enabled=None, oidc_oauth_enabled = Setting().get('oidc_oauth_enabled') if azure_oauth_enabled is None: azure_oauth_enabled = Setting().get('azure_oauth_enabled') + if saml_enabled is None: + saml_enabled = Setting().get('saml_enabled') return local_db_enabled or ldap_enabled or google_oauth_enabled or github_oauth_enabled or oidc_oauth_enabled or azure_oauth_enabled @@ -1007,6 +1010,74 @@ def setting_authentication(): 'msg': 'Saved successfully. Please reload PDA to take effect.' } + elif conf_type == 'saml': + saml_enabled = True if request.form.get('saml_enabled') else False + if not has_an_auth_method(saml_enabled=saml_enabled): + result = { + 'status': + False, + 'msg': + 'Must have at least one authentication method enabled.' + } + else: + Setting().set('saml_enabled', True) + print("SAML ENABLED = ",Setting().get('saml_enabled')) + Setting().set('saml_metadata_url', + request.form.get('saml_metadata_url')) + Setting().set('saml_metadata_cache_lifetime', + request.form.get('saml_metadata_cache_lifetime')) + Setting().set('saml_idp_sso_binding', + request.form.get('saml_idp_sso_binding')) + Setting().set('saml_idp_entity_id', + request.form.get('saml_idp_entity_id')) + Setting().set('saml_nameid_format', + request.form.get('saml_nameid_format')) + Setting().set('saml_sp_requested_attributes', + request.form.get('saml_sp_requested_attributes')) + Setting().set('saml_attribute_email', + request.form.get('saml_attribute_email')) + Setting().set('saml_attribute_givenname', + request.form.get('saml_attribute_givenname')) + Setting().set('saml_attribute_surname', + request.form.get('saml_attribute_surname')) + Setting().set('saml_attribute_username', + request.form.get('saml_attribute_username')) + Setting().set('saml_attribute_admin', + request.form.get('saml_attribute_admin')) + Setting().set('saml_attribute_account', + request.form.get('saml_attribute_account')) + Setting().set('saml_sp_entity_id', + request.form.get('saml_sp_entity_id')) + Setting().set('saml_sp_contact_name', + request.form.get('saml_sp_contact_name')) + Setting().set('saml_sp_contact_mail', + request.form.get('saml_sp_contact_mail')) + Setting().set('saml_cert_file', + request.form.get('saml_cert_file')) + Setting().set('saml_cert_key', + request.form.get('saml_cert_key')) + Setting().set('saml_sign_request', + request.form.get('saml_sign_request')) + Setting().set('saml_logout', + request.form.get('saml_logout')) + Setting().set('saml_logout_url', + request.form.get('saml_logout_url')) + Setting().set('saml_assertion_encrypted', + request.form.get('saml_assertion_encrypted')) + Setting().set( + 'saml_sign_request', + True if request.form.get('saml_sign_request') else False) + Setting().set( + 'saml_logout', + True if request.form.get('saml_logout') else False) + Setting().set( + 'saml_assertion_encrypted', + True if request.form.get('saml_assertion_encrypted') else False) + result = { + 'status': True, + 'msg': + 'Saved successfully. Please reload PDA to take effect.' + } else: return abort(400) diff --git a/powerdnsadmin/templates/admin_setting_authentication.html b/powerdnsadmin/templates/admin_setting_authentication.html index cec65f5..e368fab 100644 --- a/powerdnsadmin/templates/admin_setting_authentication.html +++ b/powerdnsadmin/templates/admin_setting_authentication.html @@ -56,6 +56,7 @@
  • Github OAuth
  • Microsoft OAuth
  • OpenID Connect OAuth
  • +
  • SAML
  • @@ -677,6 +678,270 @@
    +
    +
    +
    +
    + + +
    + GENERAL +
    + + +
    +
    +
    + METADATA +
    + + + +
    +
    + + + +
    +
    +
    + IDP +
    + + + +
    +
    + + + +
    +
    + + + +
    +
    +
    + ATTRIBUTES +
    + + + +
    +
    + + + +
    +
    + + + +
    +
    + + + +
    +
    + + + +
    +
    + + + +
    +
    + + + +
    +
    + + + +
    +
    + + + +
    +
    + + + +
    +
    +
    + CERTIFICATE +
    + + + +
    +
    + + + +
    +
    + + +
    +
    +
    + LOGOUT +
    + + +
    +
    + + + +
    +
    +
    + ADVANCED +
    + + +
    +
    +
    + +
    +
    +
    +
    + Help +
    +
    METADATA
    +

    +
      +
    • + Metadata URL - +
    • +
    • + Metadata Cache Lifetime - Cache Lifetime in Seconds +
    • +
    +
    +
    IDP
    +

    +
      +
    • + IDP SSO Binding - SAML SSO binding format to use +
    • +
    • + IDP Entity ID - EntityID of the IdP to use. Only needed if more than one IdP is in the SAML_METADATA_URL +
    • +
    • + NameID Format - NameID format to request +
    • +
    +
    +
    ATTRIBUTES
    +

    +
      +
    • + Requested Attributes - Following parameter defines RequestedAttributes section in SAML metadata + since certain iDPs require explicit attribute request. If not provided section + will not be available in metadata. +
      + Possible attributes:
      + name (mandatory), nameFormat, isRequired, friendlyName +
      + NOTE: This parameter requires to be entered in valid JSON format as displayed below + and multiple attributes can be given. +
      + Following example: +
      + SAML_SP_REQUESTED_ATTRIBUTES = '[
      + {"name": "urn:oid:0.9.2342.19200300.100.1.3", "nameFormat": "urn:oasis:names:tc:SAML:2.0:attrname-format:uri", "isRequired": true, "friendlyName": "email"},
      + {"name": "mail", "isRequired": false, "friendlyName": "test-field"}
      + ]'
      +
      + produces following metadata section: +
      + <md:AttributeConsumingService index="1">
      + <md:RequestedAttribute Name="urn:oid:0.9.2342.19200300.100.1.3" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:uri" FriendlyName="email" isRequired="true"/>
      + <md:RequestedAttribute Name="mail" FriendlyName="test-field"/>
      + </md:AttributeConsumingService>
      +
    • +
    • + Email - Attribute to use for Email address. +
    • +
    • + Given Name - Attribute to use for Given name. +
    • +
    • + Surname - Attribute to use for Surname. +
    • +
    • + Username - Attribute to use for username. +
    • +
    • + Admin - Attribute to get admin status from.
      + If set, look for the value 'true' to set a user as an administrator.
      + If not included in assertion, or set to something other than 'true', + the user is set as a non-administrator user. +
    • +
    • + Account - Attribute to get account names from.
      + If set, the user will be added and removed from accounts to match + what's in the login assertion.
      Accounts that don't exist will + be created and the user added to them. +
    • +
    • + SP Entity ID - +
    • +
    • + SP Entity Name - +
    • +
    • + SP Entity Mail - +
    • +
    +
    +
    CERTIFICATE
    +
    Configures the path to certificate file and it's respective private key file. +
      +
    • + The Cert File - Cert Key pair is used for signing metadata, encrypting tokens and all other signing/encryption + tasks during communication between iDP and SP.
      + NOTE: If these two parameters aren't explicitly provided, a self-signed certificate-key pair + will be generated in "PowerDNS-Admin" root directory.
      + CAUTION: For production use, usage of self-signed certificates is highly discouraged. + Use certificates from trusted CA instead. +
    • +
    • + Sign Request - Configures if SAML tokens should be encrypted.
      + If enabled a new app certificate will be generated on restart. +
    • +
    +
    +
    LOGOUT
    +
    + Choose whether user is logged out of SAML session and possibly redirect them elsewhere. +
      +
    • + SAML Logout - Use SAML standard logout mechanism retreived from idp metadata.
      + If configured false don't care about SAML session on logout.
      + Logout from PowerDNS-Admin only and keep SAML session authenticated. +
    • +
    • + Logout URL - Configure to redirect to a url different than PowerDNS-Admin login after a successful SAML logout. +
    • +
    +
    +
    +
    +
    +
    @@ -1050,6 +1315,91 @@ {% endif %} //END: OIDC Tab JS + // START: OIDC tab js + $('#saml_enabled').iCheck({ + checkboxClass : 'icheckbox_square-blue', + increaseArea : '20%' + }).on('ifChanged', function(e) { + var is_enabled = e.currentTarget.checked; + if (is_enabled){ + $('#saml_metadata_url').prop('required', true); + $('#saml_metadata_cache_lifetime').prop('required', true); + $('#saml_idp_sso_binding').prop('required', true); + $('#saml_idp_entity_id').prop('required', true); + $('#saml_nameid_format').prop('required', true); + $('#saml_sp_requested_attributes').prop('required', true); + $('#saml_attribute_email').prop('required', true); + $('#saml_attribute_givenname').prop('required', true); + $('#saml_attribute_surname').prop('required', true); + $('#saml_attribute_username').prop('required', true); + $('#saml_attribute_admin').prop('required', true); + $('#saml_attribute_account').prop('required', true); + $('#saml_sp_entity_id').prop('required', true); + $('#saml_sp_contact_name').prop('required', true); + $('#saml_sp_contact_mail').prop('required', true); + $('#saml_cert_file').prop('required', true); + $('#saml_cert_key').prop('required', true); + $('#saml_logout_url').prop('required', true); + } else { + $('#saml_metadata_url').prop('required', false); + $('#saml_metadata_cache_lifetime').prop('required', false); + $('#saml_idp_sso_binding').prop('required', false); + $('#saml_idp_entity_id').prop('required', false); + $('#saml_nameid_format').prop('required', false); + $('#saml_sp_requested_attributes').prop('required', false); + $('#saml_attribute_email').prop('required', false); + $('#saml_attribute_givenname').prop('required', false); + $('#saml_attribute_surname').prop('required', false); + $('#saml_attribute_username').prop('required', false); + $('#saml_attribute_admin').prop('required', false); + $('#saml_attribute_account').prop('required', false); + $('#saml_sp_entity_id').prop('required', false); + $('#saml_sp_contact_name').prop('required', false); + $('#saml_sp_contact_mail').prop('required', false); + $('#saml_cert_file').prop('required', false); + $('#saml_cert_key').prop('required', false); + $('#saml_logout_url').prop('required', false); + } + }); + // init validation requirement at first time page load + {% if SETTING.get('saml_enabled') %} + $('#saml_metadata_url').prop('required', true); + $('#saml_metadata_cache_lifetime').prop('required', true); + $('#saml_idp_sso_binding').prop('required', true); + $('#saml_idp_entity_id').prop('required', true); + $('#saml_nameid_format').prop('required', true); + $('#saml_sp_requested_attributes').prop('required', true); + $('#saml_attribute_email').prop('required', true); + $('#saml_attribute_givenname').prop('required', true); + $('#saml_attribute_surname').prop('required', true); + $('#saml_attribute_username').prop('required', true); + $('#saml_attribute_admin').prop('required', true); + $('#saml_attribute_account').prop('required', true); + $('#saml_sp_entity_id').prop('required', true); + $('#saml_sp_contact_name').prop('required', true); + $('#saml_sp_contact_mail').prop('required', true); + $('#saml_cert_file').prop('required', true); + $('#saml_cert_key').prop('required', true); + $('#saml_sign_request').prop('required', true); + $('#saml_logout').prop('required', true); + $('#saml_logout_url').prop('required', true); + $('#saml_assertion_encrypted').prop('required', true); + {% endif %} + + $('#saml_sign_request').iCheck({ + checkboxClass : 'icheckbox_square-blue', + increaseArea : '20%' + }) + $('#saml_logout').iCheck({ + checkboxClass : 'icheckbox_square-blue', + increaseArea : '20%' + }) + $('#saml_assertion_encrypted').iCheck({ + checkboxClass : 'icheckbox_square-blue', + increaseArea : '20%' + }) + // END: OIDC Tab js + {% endblock %} From 3255bc26d01f32510560240403f3a76acfb2dc81 Mon Sep 17 00:00:00 2001 From: vmarkop Date: Tue, 7 Dec 2021 10:29:32 +0200 Subject: [PATCH 02/20] Migrated settings from app.config to Settings() --- configs/development.py | 4 +- powerdnsadmin/default_config.py | 4 +- powerdnsadmin/models/setting.py | 21 +++-- powerdnsadmin/routes/admin.py | 1 - powerdnsadmin/routes/index.py | 78 ++++++++++------- powerdnsadmin/services/saml.py | 84 ++++++++----------- .../admin_setting_authentication.html | 68 +++++++++++++-- 7 files changed, 163 insertions(+), 97 deletions(-) diff --git a/configs/development.py b/configs/development.py index 06e32bc..d4529f6 100644 --- a/configs/development.py +++ b/configs/development.py @@ -32,9 +32,9 @@ SQLALCHEMY_DATABASE_URI = 'sqlite:///' + os.path.join(basedir, 'pdns.db') # MAIL_DEFAULT_SENDER = ('PowerDNS-Admin', 'noreply@domain.ltd') # SAML Authnetication -SAML_ENABLED = False +# SAML_ENABLED = True # SAML_DEBUG = True -# SAML_PATH = os.path.join(os.path.dirname(__file__), 'saml') +SAML_PATH = os.path.join(os.path.dirname(__file__), 'saml') # ##Example for ADFS Metadata-URL # SAML_METADATA_URL = 'https:///FederationMetadata/2007-06/FederationMetadata.xml' # #Cache Lifetime in Seconds diff --git a/powerdnsadmin/default_config.py b/powerdnsadmin/default_config.py index 42b26b4..1b775e7 100644 --- a/powerdnsadmin/default_config.py +++ b/powerdnsadmin/default_config.py @@ -24,5 +24,5 @@ SQLALCHEMY_DATABASE_URI = 'mysql://'+SQLA_DB_USER+':'+SQLA_DB_PASSWORD+'@'+SQLA_ # SQLALCHEMY_DATABASE_URI = 'sqlite:///' + os.path.join(basedir, 'pdns.db') # SAML Authnetication -SAML_ENABLED = False -SAML_ASSERTION_ENCRYPTED = True +# SAML_ENABLED = False +# SAML_ASSERTION_ENCRYPTED = True diff --git a/powerdnsadmin/models/setting.py b/powerdnsadmin/models/setting.py index 86bb950..470eaef 100644 --- a/powerdnsadmin/models/setting.py +++ b/powerdnsadmin/models/setting.py @@ -111,10 +111,11 @@ class Setting(db.Model): 'oidc_oauth_account_name_property': '', 'oidc_oauth_account_description_property': '', 'saml_enabled': False, - 'saml_metadata_url': 'https:///FederationMetadata/2007-06/FederationMetadata.xml', + 'saml_debug': True, + 'saml_metadata_url': 'https://md.aai.grnet.gr/aggregates/grnet-metadata.xml',#'https://md.aai.grnet.gr/aggregates/grnet-metadata.xml' 'saml_metadata_cache_lifetime': '1', - 'saml_idp_sso_binding': 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST', - 'saml_idp_entity_id': 'https://idp.example.edu/idp', + 'saml_idp_sso_binding': 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect',# + 'saml_idp_entity_id': 'https://idp.uoa.gr/idp/shibboleth',#'https://idp.uoa.gr/idp/shibboleth' 'saml_nameid_format': 'urn:oid:0.9.2342.19200300.100.1.1', 'saml_sp_requested_attributes': '[ \ {"name": "urn:oid:0.9.2342.19200300.100.1.3", "nameFormat": "urn:oasis:names:tc:SAML:2.0:attrname-format:uri", "isRequired": true, "friendlyName": "email"}, \ @@ -126,15 +127,25 @@ class Setting(db.Model): 'saml_attribute_username': 'urn:oid:0.9.2342.19200300.100.1.1', 'saml_attribute_admin': 'https://example.edu/pdns-admin', 'saml_attribute_account': 'https://example.edu/pdns-account', + 'saml_attribute_group': None, + 'saml_group_admin_name': None, + 'saml_group_to_account_mapping': None, 'saml_sp_entity_id': 'http://', - 'saml_sp_entity_name': '', - 'saml_sp_entity_mail': '', + 'saml_sp_contact_name': '', + 'saml_sp_contact_mail': '', 'saml_cert_file': '/etc/pki/powerdns-admin/cert.crt', 'saml_cert_key': '/etc/pki/powerdns-admin/key.pem', 'saml_sign_request': False, 'saml_logout': False, 'saml_logout_url': 'https://google.com', 'saml_assertion_encrypted': True, + 'saml_digest_algorithm': 'http://www.w3.org/2000/09/xmldsig#rsa-sha1', + 'saml_signature_algorithm': 'http://www.w3.org/2000/09/xmldsig#rsa-sha1', + 'saml_want_assertions_signed': True, + 'saml_sign_metadata': True, + 'saml_want_message_signed': True, + 'saml_metadata_cache_duration': 'PT5M', + 'saml_metadata_valid_until': '', 'forward_records_allow_edit': { 'A': True, 'AAAA': True, diff --git a/powerdnsadmin/routes/admin.py b/powerdnsadmin/routes/admin.py index bbe3f4c..2ba3b39 100644 --- a/powerdnsadmin/routes/admin.py +++ b/powerdnsadmin/routes/admin.py @@ -1021,7 +1021,6 @@ def setting_authentication(): } else: Setting().set('saml_enabled', True) - print("SAML ENABLED = ",Setting().get('saml_enabled')) Setting().set('saml_metadata_url', request.form.get('saml_metadata_url')) Setting().set('saml_metadata_cache_lifetime', diff --git a/powerdnsadmin/routes/index.py b/powerdnsadmin/routes/index.py index ccdcd6b..2d65fd9 100644 --- a/powerdnsadmin/routes/index.py +++ b/powerdnsadmin/routes/index.py @@ -141,7 +141,7 @@ def oidc_login(): @index_bp.route('/login', methods=['GET', 'POST']) def login(): - SAML_ENABLED = current_app.config.get('SAML_ENABLED') + SAML_ENABLED = Setting().get('saml_enabled') if g.user is not None and current_user.is_authenticated: return redirect(url_for('dashboard.dashboard')) @@ -587,18 +587,17 @@ def get_azure_groups(uri): @index_bp.route('/logout') def logout(): - if current_app.config.get( - 'SAML_ENABLED' - ) and 'samlSessionIndex' in session and current_app.config.get( - 'SAML_LOGOUT'): + if Setting().get('saml_enabled' + ) and 'samlSessionIndex' in session and Setting().get( + 'saml_logout'): req = saml.prepare_flask_request(request) auth = saml.init_saml_auth(req) - if current_app.config.get('SAML_LOGOUT_URL'): + if Setting().get('saml_logout_url'): return redirect( auth.logout( name_id_format= "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress", - return_to=current_app.config.get('SAML_LOGOUT_URL'), + return_to=Setting().get('saml_logout_url'), session_index=session['samlSessionIndex'], name_id=session['samlNameId'])) return redirect( @@ -896,7 +895,7 @@ def dyndns_update(): ### START SAML AUTHENTICATION ### @index_bp.route('/saml/login') def saml_login(): - if not current_app.config.get('SAML_ENABLED'): + if not Setting().get('saml_enabled'): abort(400) req = saml.prepare_flask_request(request) auth = saml.init_saml_auth(req) @@ -907,7 +906,7 @@ def saml_login(): @index_bp.route('/saml/metadata') def saml_metadata(): - if not current_app.config.get('SAML_ENABLED'): + if not Setting().get('saml_enabled'): current_app.logger.error("SAML authentication is disabled.") abort(400) @@ -928,7 +927,7 @@ def saml_metadata(): @index_bp.route('/saml/authorized', methods=['GET', 'POST']) def saml_authorized(): errors = [] - if not current_app.config.get('SAML_ENABLED'): + if not Setting().get('saml_enabled'): current_app.logger.error("SAML authentication is disabled.") abort(400) req = saml.prepare_flask_request(request) @@ -945,9 +944,9 @@ def saml_authorized(): if 'RelayState' in request.form and self_url != request.form[ 'RelayState']: return redirect(auth.redirect_to(request.form['RelayState'])) - if current_app.config.get('SAML_ATTRIBUTE_USERNAME', False): + if Setting().get('saml_attribute_username'): username = session['samlUserdata'][ - current_app.config['SAML_ATTRIBUTE_USERNAME']][0].lower() + Setting().get('saml_attribute_username')][0].lower() else: username = session['samlNameId'].lower() user = User.query.filter_by(username=username).first() @@ -958,22 +957,38 @@ def saml_authorized(): email=session['samlNameId']) user.create_local_user() session['user_id'] = user.id - email_attribute_name = current_app.config.get('SAML_ATTRIBUTE_EMAIL', - 'email') - givenname_attribute_name = current_app.config.get( - 'SAML_ATTRIBUTE_GIVENNAME', 'givenname') - surname_attribute_name = current_app.config.get( - 'SAML_ATTRIBUTE_SURNAME', 'surname') - name_attribute_name = current_app.config.get('SAML_ATTRIBUTE_NAME', - None) - account_attribute_name = current_app.config.get( - 'SAML_ATTRIBUTE_ACCOUNT', None) - admin_attribute_name = current_app.config.get('SAML_ATTRIBUTE_ADMIN', - None) - group_attribute_name = current_app.config.get('SAML_ATTRIBUTE_GROUP', - None) - admin_group_name = current_app.config.get('SAML_GROUP_ADMIN_NAME', - None) + if Setting().get('saml_attribute_email'): + email_attribute_name = Setting().get('saml_attribute_email') + else: + email_attribute_name = 'email' + if Setting().get('saml_attribute_givenname'): + givenname_attribute_name = Setting().get('saml_attribute_givenname') + else: + givenname_attribute_name = 'givenname' + if Setting().get('saml_attribute_surname'): + surname_attribute_name = Setting().get('saml_attribute_surname') + else: + surname_attribute_name = 'surname' + if Setting().get('saml_attribute_name'): + name_attribute_name = Setting().get('saml_attribute_name') + else: + name_attribute_name = None + if Setting().get('saml_attribute_account'): + account_attribute_name = Setting().get('saml_attribute_account') + else: + account_attribute_name = None + if Setting().get('saml_attribute_admin'): + admin_attribute_name = Setting().get('saml_attribute_admin') + else: + admin_attribute_name = None + if Setting().get('saml_attribute_group'): + group_attribute_name = Setting().get('saml_attribute_group') + else: + group_attribute_name = None + if Setting().get('saml_group_admin_name'): + admin_group_name = Setting().get('saml_group_admin_name') + else: + admin_group_name = None group_to_account_mapping = create_group_to_account_mapping() if email_attribute_name in session['samlUserdata']: @@ -1045,8 +1060,7 @@ def saml_authorized(): def create_group_to_account_mapping(): - group_to_account_mapping_string = current_app.config.get( - 'SAML_GROUP_TO_ACCOUNT_MAPPING', None) + group_to_account_mapping_string = Setting().get('saml_group_to_account_mapping') if group_to_account_mapping_string and len( group_to_account_mapping_string.strip()) > 0: group_to_account_mapping = group_to_account_mapping_string.split(',') @@ -1096,8 +1110,8 @@ def saml_logout(): clear_session() if url is not None: return redirect(url) - elif current_app.config.get('SAML_LOGOUT_URL') is not None: - return redirect(current_app.config.get('SAML_LOGOUT_URL')) + elif Setting().get('saml_logout_url') is not None: + return redirect(Setting().get('saml_logout_url')) else: return redirect(url_for('login')) else: diff --git a/powerdnsadmin/services/saml.py b/powerdnsadmin/services/saml.py index 40c97bf..498e18a 100644 --- a/powerdnsadmin/services/saml.py +++ b/powerdnsadmin/services/saml.py @@ -6,11 +6,12 @@ import os from ..lib.certutil import KEY_FILE, CERT_FILE, create_self_signed_cert from ..lib.utils import urlparse +from ..models.setting import Setting class SAML(object): def __init__(self): - if current_app.config['SAML_ENABLED']: + if Setting().get('saml_enabled'): from onelogin.saml2.auth import OneLogin_Saml2_Auth from onelogin.saml2.idp_metadata_parser import OneLogin_Saml2_IdPMetadataParser @@ -19,18 +20,15 @@ class SAML(object): self.OneLogin_Saml2_IdPMetadataParser = OneLogin_Saml2_IdPMetadataParser self.idp_data = None - if 'SAML_IDP_ENTITY_ID' in current_app.config: + if Setting().get('saml_idp_entity_id'): self.idp_data = OneLogin_Saml2_IdPMetadataParser.parse_remote( - current_app.config['SAML_METADATA_URL'], - entity_id=current_app.config.get('SAML_IDP_ENTITY_ID', - None), - required_sso_binding=current_app. - config['SAML_IDP_SSO_BINDING']) + Setting().get('saml_metadata_url'), + entity_id=Setting().get('saml_idp_entity_id'), + required_sso_binding=Setting().get('saml_idp_sso_binding')) else: self.idp_data = OneLogin_Saml2_IdPMetadataParser.parse_remote( - current_app.config['SAML_METADATA_URL'], - entity_id=current_app.config.get('SAML_IDP_ENTITY_ID', - None)) + Setting().get('saml_metadata_url'), + entity_id=None) if self.idp_data is None: current_app.logger.info( 'SAML: IDP Metadata initial load failed') @@ -39,7 +37,7 @@ class SAML(object): def get_idp_data(self): lifetime = timedelta( - minutes=current_app.config['SAML_METADATA_CACHE_LIFETIME']) + minutes=int(Setting().get('saml_metadata_cache_lifetime'))) # should be seconds instead of minutes? if self.idp_timestamp + lifetime < datetime.now(): background_thread = Thread(target=self.retrieve_idp_data()) @@ -49,22 +47,22 @@ class SAML(object): def retrieve_idp_data(self): - if 'SAML_IDP_SSO_BINDING' in current_app.config: + if Setting().get('saml_idp_sso_binding'): new_idp_data = self.OneLogin_Saml2_IdPMetadataParser.parse_remote( - current_app.config['SAML_METADATA_URL'], - entity_id=current_app.config.get('SAML_IDP_ENTITY_ID', None), - required_sso_binding=current_app.config['SAML_IDP_SSO_BINDING'] + Setting().get('saml_metadata_url'), + entity_id=Setting().get('saml_idp_entity_id'), + required_sso_binding=Setting().get('saml_idp_sso_binding') ) else: new_idp_data = self.OneLogin_Saml2_IdPMetadataParser.parse_remote( - current_app.config['SAML_METADATA_URL'], - entity_id=current_app.config.get('SAML_IDP_ENTITY_ID', None)) + Setting().get('saml_metadata_url'), + entity_id=Setting().get('saml_idp_entity_id')) if new_idp_data is not None: self.idp_data = new_idp_data self.idp_timestamp = datetime.now() current_app.logger.info( "SAML: IDP Metadata successfully retrieved from: " + - current_app.config['SAML_METADATA_URL']) + Setting().get('saml_metadata_url')) else: current_app.logger.info( "SAML: IDP Metadata could not be retrieved") @@ -94,20 +92,19 @@ class SAML(object): metadata = self.get_idp_data() settings = {} settings['sp'] = {} - if 'SAML_NAMEID_FORMAT' in current_app.config: - settings['sp']['NameIDFormat'] = current_app.config[ - 'SAML_NAMEID_FORMAT'] + if Setting().get('saml_nameid_format'): + settings['sp']['NameIDFormat'] = Setting().get('saml_nameid_format') else: settings['sp']['NameIDFormat'] = self.idp_data.get('sp', {}).get( 'NameIDFormat', 'urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified') - settings['sp']['entityId'] = current_app.config['SAML_SP_ENTITY_ID'] + settings['sp']['entityId'] = Setting().get('saml_sp_entity_id') - if ('SAML_CERT' in current_app.config) and ('SAML_KEY' in current_app.config): + if (Setting().get('saml_cert_file')) and (Setting().get('saml_cert_key')): - saml_cert_file = current_app.config['SAML_CERT'] - saml_key_file = current_app.config['SAML_KEY'] + saml_cert_file = Setting().get('saml_cert_file') + saml_key_file = Setting().get('saml_cert_key') if os.path.isfile(saml_cert_file): cert = open(saml_cert_file, "r").readlines() @@ -130,8 +127,8 @@ class SAML(object): settings['sp']['privateKey'] = "".join(key) - if 'SAML_SP_REQUESTED_ATTRIBUTES' in current_app.config: - saml_req_attr = json.loads(current_app.config['SAML_SP_REQUESTED_ATTRIBUTES']) + if Setting().get('saml_sp_requested_attributes'): + saml_req_attr = json.loads(Setting().get('saml_sp_requested_attributes')) settings['sp']['attributeConsumingService'] = { "serviceName": "PowerDNSAdmin", "serviceDescription": "PowerDNS-Admin - PowerDNS administration utility", @@ -152,7 +149,7 @@ class SAML(object): settings['sp']['singleLogoutService']['url'] = own_url + '/saml/sls' settings['idp'] = metadata['idp'] settings['strict'] = True - settings['debug'] = current_app.config['SAML_DEBUG'] + settings['debug'] = Setting().get('saml_debug') settings['security'] = {} settings['security'][ 'digestAlgorithm'] = 'http://www.w3.org/2001/04/xmldsig-more#rsa-sha256' @@ -161,33 +158,24 @@ class SAML(object): settings['security']['requestedAuthnContext'] = True settings['security'][ 'signatureAlgorithm'] = 'http://www.w3.org/2001/04/xmldsig-more#rsa-sha256' - settings['security']['wantAssertionsEncrypted'] = current_app.config.get( - 'SAML_ASSERTION_ENCRYPTED', True) + settings['security']['wantAssertionsEncrypted'] = Setting().get('saml_assertion_encrypted') settings['security']['wantAttributeStatement'] = True settings['security']['wantNameId'] = True - settings['security']['authnRequestsSigned'] = current_app.config[ - 'SAML_SIGN_REQUEST'] - settings['security']['logoutRequestSigned'] = current_app.config[ - 'SAML_SIGN_REQUEST'] - settings['security']['logoutResponseSigned'] = current_app.config[ - 'SAML_SIGN_REQUEST'] + settings['security']['authnRequestsSigned'] = Setting().get('saml_sign_request') + settings['security']['logoutRequestSigned'] = Setting().get('saml_sign_request') + settings['security']['logoutResponseSigned'] = Setting().get('saml_sign_request') settings['security']['nameIdEncrypted'] = False - settings['security']['signMetadata'] = True - settings['security']['wantAssertionsSigned'] = True - settings['security']['wantMessagesSigned'] = current_app.config.get( - 'SAML_WANT_MESSAGE_SIGNED', True) + settings['security']['signMetadata'] = Setting().get('saml_sign_metadata') + settings['security']['wantAssertionsSigned'] = Setting().get('saml_want_assertions_signed') + settings['security']['wantMessagesSigned'] = Setting().get('saml_want_message_signed') settings['security']['wantNameIdEncrypted'] = False settings['contactPerson'] = {} settings['contactPerson']['support'] = {} - settings['contactPerson']['support'][ - 'emailAddress'] = current_app.config['SAML_SP_CONTACT_NAME'] - settings['contactPerson']['support']['givenName'] = current_app.config[ - 'SAML_SP_CONTACT_MAIL'] + settings['contactPerson']['support']['emailAddress'] = Setting().get('saml_sp_contact_mail') + settings['contactPerson']['support']['givenName'] = Setting().get('saml_sp_contact_name') settings['contactPerson']['technical'] = {} - settings['contactPerson']['technical'][ - 'emailAddress'] = current_app.config['SAML_SP_CONTACT_MAIL'] - settings['contactPerson']['technical'][ - 'givenName'] = current_app.config['SAML_SP_CONTACT_NAME'] + settings['contactPerson']['technical']['emailAddress'] = Setting().get('saml_sp_contact_mail') + settings['contactPerson']['technical']['givenName'] = Setting().get('saml_sp_contact_name') settings['organization'] = {} settings['organization']['en-US'] = {} settings['organization']['en-US']['displayname'] = 'PowerDNS-Admin' diff --git a/powerdnsadmin/templates/admin_setting_authentication.html b/powerdnsadmin/templates/admin_setting_authentication.html index e368fab..06b5a90 100644 --- a/powerdnsadmin/templates/admin_setting_authentication.html +++ b/powerdnsadmin/templates/admin_setting_authentication.html @@ -766,12 +766,12 @@
    - +
    - +
    @@ -800,7 +800,7 @@
    - +
    @@ -810,6 +810,38 @@ +
    + + +
    +
    + + + +
    +
    + + + +
    +
    + + +
    +
    + + +
    +
    + + + +
    +
    + + + +
    @@ -1315,7 +1347,7 @@ {% endif %} //END: OIDC Tab JS - // START: OIDC tab js + // START: SAML tab js $('#saml_enabled').iCheck({ checkboxClass : 'icheckbox_square-blue', increaseArea : '20%' @@ -1340,6 +1372,10 @@ $('#saml_cert_file').prop('required', true); $('#saml_cert_key').prop('required', true); $('#saml_logout_url').prop('required', true); + $('#saml_digest_algorithm').prop('required', true); + $('#saml_signature_algorithm').prop('required', true); + $('#saml_metadata_cache_duration').prop('required', true); + $('#saml_metadata_valid_until').prop('required', true); } else { $('#saml_metadata_url').prop('required', false); $('#saml_metadata_cache_lifetime').prop('required', false); @@ -1359,6 +1395,10 @@ $('#saml_cert_file').prop('required', false); $('#saml_cert_key').prop('required', false); $('#saml_logout_url').prop('required', false); + $('#saml_digest_algorithm').prop('required', false); + $('#saml_signature_algorithm').prop('required', false); + $('#saml_metadata_cache_duration').prop('required', false); + $('#saml_metadata_valid_until').prop('required', false); } }); // init validation requirement at first time page load @@ -1380,10 +1420,12 @@ $('#saml_sp_contact_mail').prop('required', true); $('#saml_cert_file').prop('required', true); $('#saml_cert_key').prop('required', true); - $('#saml_sign_request').prop('required', true); $('#saml_logout').prop('required', true); $('#saml_logout_url').prop('required', true); - $('#saml_assertion_encrypted').prop('required', true); + $('#saml_digest_algorithm').prop('required', true); + $('#saml_signature_algorithm').prop('required', true); + $('#saml_metadata_cache_duration').prop('required', true); + $('#saml_metadata_valid_until').prop('required', true); {% endif %} $('#saml_sign_request').iCheck({ @@ -1398,7 +1440,19 @@ checkboxClass : 'icheckbox_square-blue', increaseArea : '20%' }) - // END: OIDC Tab js + $('#saml_want_assertions_signed').iCheck({ + checkboxClass : 'icheckbox_square-blue', + increaseArea : '20%' + }) + $('#saml_want_message_signed').iCheck({ + checkboxClass : 'icheckbox_square-blue', + increaseArea : '20%' + }) + $('#saml_sign_metadata').iCheck({ + checkboxClass : 'icheckbox_square-blue', + increaseArea : '20%' + }) + // END: SAML Tab js {% endblock %} From c08f9b1cfdaa69fbbf19fe2b7dff2fdeec170600 Mon Sep 17 00:00:00 2001 From: vmarkop Date: Tue, 7 Dec 2021 15:32:31 +0200 Subject: [PATCH 03/20] Migrated SAML settings from app.config to Settings(), SAML exception catching --- powerdnsadmin/models/setting.py | 6 +- powerdnsadmin/routes/admin.py | 4 +- powerdnsadmin/routes/index.py | 34 +++++++--- powerdnsadmin/services/saml.py | 62 ++++++++++++------- .../admin_setting_authentication.html | 2 +- 5 files changed, 72 insertions(+), 36 deletions(-) diff --git a/powerdnsadmin/models/setting.py b/powerdnsadmin/models/setting.py index 470eaef..30cbdf7 100644 --- a/powerdnsadmin/models/setting.py +++ b/powerdnsadmin/models/setting.py @@ -112,10 +112,10 @@ class Setting(db.Model): 'oidc_oauth_account_description_property': '', 'saml_enabled': False, 'saml_debug': True, - 'saml_metadata_url': 'https://md.aai.grnet.gr/aggregates/grnet-metadata.xml',#'https://md.aai.grnet.gr/aggregates/grnet-metadata.xml' + 'saml_metadata_url': 'https://example.com/metadata.xml', 'saml_metadata_cache_lifetime': '1', - 'saml_idp_sso_binding': 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect',# - 'saml_idp_entity_id': 'https://idp.uoa.gr/idp/shibboleth',#'https://idp.uoa.gr/idp/shibboleth' + 'saml_idp_sso_binding': 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect', + 'saml_idp_entity_id': 'https://idp.example.com/idp/', 'saml_nameid_format': 'urn:oid:0.9.2342.19200300.100.1.1', 'saml_sp_requested_attributes': '[ \ {"name": "urn:oid:0.9.2342.19200300.100.1.3", "nameFormat": "urn:oasis:names:tc:SAML:2.0:attrname-format:uri", "isRequired": true, "friendlyName": "email"}, \ diff --git a/powerdnsadmin/routes/admin.py b/powerdnsadmin/routes/admin.py index 2ba3b39..b18d60c 100644 --- a/powerdnsadmin/routes/admin.py +++ b/powerdnsadmin/routes/admin.py @@ -1020,7 +1020,9 @@ def setting_authentication(): 'Must have at least one authentication method enabled.' } else: - Setting().set('saml_enabled', True) + Setting().set( + 'saml_enabled', + True if request.form.get('saml_enabled') else False) Setting().set('saml_metadata_url', request.form.get('saml_metadata_url')) Setting().set('saml_metadata_cache_lifetime', diff --git a/powerdnsadmin/routes/index.py b/powerdnsadmin/routes/index.py index 2d65fd9..18e18d1 100644 --- a/powerdnsadmin/routes/index.py +++ b/powerdnsadmin/routes/index.py @@ -593,19 +593,27 @@ def logout(): req = saml.prepare_flask_request(request) auth = saml.init_saml_auth(req) if Setting().get('saml_logout_url'): + try: + return redirect( + auth.logout( + name_id_format= + Setting().get('saml_nameid_format'), + return_to=Setting().get('saml_logout_url'), + session_index=session['samlSessionIndex'], + name_id=session['samlNameId'])) + except: + current_app.logger.info( + "SAML: Your IDP does not support Single Logout.") + try: return redirect( auth.logout( name_id_format= - "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress", - return_to=Setting().get('saml_logout_url'), + Setting().get('saml_nameid_format'), session_index=session['samlSessionIndex'], name_id=session['samlNameId'])) - return redirect( - auth.logout( - name_id_format= - "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress", - session_index=session['samlSessionIndex'], - name_id=session['samlNameId'])) + except: + current_app.logger.info( + "SAML: Your IDP does not support Single Logout.") redirect_uri = url_for('index.login') oidc_logout = Setting().get('oidc_oauth_logout_url') @@ -897,8 +905,16 @@ def dyndns_update(): def saml_login(): if not Setting().get('saml_enabled'): abort(400) + global saml req = saml.prepare_flask_request(request) - auth = saml.init_saml_auth(req) + try: + auth = saml.init_saml_auth(req) + except: + current_app.logger.info( + "SAML: IDP Metadata were not successfully initialized. Reinitializing...") + saml = SAML() + req = saml.prepare_flask_request(request) + auth = saml.init_saml_auth(req) redirect_url = OneLogin_Saml2_Utils.get_self_url(req) + url_for( 'index.saml_authorized') return redirect(auth.login(return_to=redirect_url)) diff --git a/powerdnsadmin/services/saml.py b/powerdnsadmin/services/saml.py index 498e18a..8161ad1 100644 --- a/powerdnsadmin/services/saml.py +++ b/powerdnsadmin/services/saml.py @@ -21,42 +21,60 @@ class SAML(object): self.idp_data = None if Setting().get('saml_idp_entity_id'): - self.idp_data = OneLogin_Saml2_IdPMetadataParser.parse_remote( - Setting().get('saml_metadata_url'), - entity_id=Setting().get('saml_idp_entity_id'), - required_sso_binding=Setting().get('saml_idp_sso_binding')) + try: + self.idp_data = OneLogin_Saml2_IdPMetadataParser.parse_remote( + Setting().get('saml_metadata_url'), + entity_id=Setting().get('saml_idp_entity_id'), + required_sso_binding=Setting().get('saml_idp_sso_binding')) + except: + self.idp_data = None else: - self.idp_data = OneLogin_Saml2_IdPMetadataParser.parse_remote( - Setting().get('saml_metadata_url'), - entity_id=None) + try: + self.idp_data = OneLogin_Saml2_IdPMetadataParser.parse_remote( + Setting().get('saml_metadata_url'), + entity_id=None) + except: + self.idp_data = None if self.idp_data is None: current_app.logger.info( 'SAML: IDP Metadata initial load failed') - exit(-1) + Setting().set('saml_enabled', False) + print("SAML EN1 ", Setting().get('saml_enabled')) + # exit(-1) def get_idp_data(self): - lifetime = timedelta( - minutes=int(Setting().get('saml_metadata_cache_lifetime'))) # should be seconds instead of minutes? - - if self.idp_timestamp + lifetime < datetime.now(): - background_thread = Thread(target=self.retrieve_idp_data()) - background_thread.start() + # lifetime = timedelta( + # minutes=int(Setting().get('saml_metadata_cache_lifetime'))) # should be seconds instead of minutes? + # Since SAML is now user-configurable, idp_data may change before the lifetime has ended, + # so metadata should not be cached at all, or outdated settings may be used. + try: + self.retrieve_idp_data() + except: + return None + # if self.idp_timestamp + lifetime < datetime.now(): + background_thread = Thread(target=self.retrieve_idp_data()) + background_thread.start() return self.idp_data def retrieve_idp_data(self): if Setting().get('saml_idp_sso_binding'): - new_idp_data = self.OneLogin_Saml2_IdPMetadataParser.parse_remote( - Setting().get('saml_metadata_url'), - entity_id=Setting().get('saml_idp_entity_id'), - required_sso_binding=Setting().get('saml_idp_sso_binding') - ) + try: + new_idp_data = self.OneLogin_Saml2_IdPMetadataParser.parse_remote( + Setting().get('saml_metadata_url'), + entity_id=Setting().get('saml_idp_entity_id'), + required_sso_binding=Setting().get('saml_idp_sso_binding')) + except: + new_idp_data = None else: - new_idp_data = self.OneLogin_Saml2_IdPMetadataParser.parse_remote( - Setting().get('saml_metadata_url'), - entity_id=Setting().get('saml_idp_entity_id')) + try: + new_idp_data = self.OneLogin_Saml2_IdPMetadataParser.parse_remote( + Setting().get('saml_metadata_url'), + entity_id=Setting().get('saml_idp_entity_id')) + except: + new_idp_data = None if new_idp_data is not None: self.idp_data = new_idp_data self.idp_timestamp = datetime.now() diff --git a/powerdnsadmin/templates/admin_setting_authentication.html b/powerdnsadmin/templates/admin_setting_authentication.html index 06b5a90..34faa55 100644 --- a/powerdnsadmin/templates/admin_setting_authentication.html +++ b/powerdnsadmin/templates/admin_setting_authentication.html @@ -829,7 +829,7 @@
    - +
    From 1ba34cf3405ab5e8d9340d271d7076a2f1b2c22f Mon Sep 17 00:00:00 2001 From: kkmanos Date: Tue, 7 Dec 2021 15:54:17 +0200 Subject: [PATCH 04/20] added wsgi file --- powerdnsadmin.wsgi | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 powerdnsadmin.wsgi diff --git a/powerdnsadmin.wsgi b/powerdnsadmin.wsgi new file mode 100644 index 0000000..1db39ab --- /dev/null +++ b/powerdnsadmin.wsgi @@ -0,0 +1,11 @@ +#!/usr/bin/env python3 +import sys +sys.path.insert(0, '/var/www/powerdns-admin/') + +activate_this = '/var/www/powerdns-admin/flask/bin/activate_this.py' +with open(activate_this) as file_: + exec(file_.read(), dict(__file__=activate_this)) + +from powerdnsadmin import create_app +application = create_app(config='../configs/production.py') +application.secret_key = "secret" From ec2fa462b1c8114274a98e7f20409da6c50b8b94 Mon Sep 17 00:00:00 2001 From: kkmanos Date: Tue, 7 Dec 2021 17:11:10 +0200 Subject: [PATCH 05/20] f --- powerdnsadmin/models/setting.py | 2 +- powerdnsadmin/routes/admin.py | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/powerdnsadmin/models/setting.py b/powerdnsadmin/models/setting.py index f1185df..be0f3a5 100644 --- a/powerdnsadmin/models/setting.py +++ b/powerdnsadmin/models/setting.py @@ -110,7 +110,7 @@ class Setting(db.Model): 'oidc_oauth_email': 'email', 'oidc_oauth_account_name_property': '', 'oidc_oauth_account_description_property': '', - 'saml_enabled': False, + 'saml_enabled': True, 'saml_debug': True, 'saml_metadata_url': 'https://example.com/metadata.xml', 'saml_metadata_cache_lifetime': '1', diff --git a/powerdnsadmin/routes/admin.py b/powerdnsadmin/routes/admin.py index 85e3f95..c223877 100644 --- a/powerdnsadmin/routes/admin.py +++ b/powerdnsadmin/routes/admin.py @@ -22,7 +22,7 @@ from ..models.domain_template import DomainTemplate from ..models.domain_template_record import DomainTemplateRecord from ..models.api_key import ApiKey from ..models.base import db - +from ..services.saml import SAML from ..lib.schema import ApiPlainKeySchema apikey_plain_schema = ApiPlainKeySchema(many=True) @@ -1694,6 +1694,8 @@ def setting_authentication(): 'msg': 'Saved successfully. Please reload PDA to take effect.' } + global saml + saml = SAML() else: return abort(400) From 0f8b8984a20e38ac3cb95a80674bacfdee2abe69 Mon Sep 17 00:00:00 2001 From: vmarkop Date: Wed, 8 Dec 2021 13:37:17 +0200 Subject: [PATCH 06/20] Added SAML Autoprovisioning --- powerdnsadmin/models/setting.py | 4 ++ powerdnsadmin/models/user.py | 7 +- powerdnsadmin/routes/index.py | 117 ++++++++++++++++++++------------ 3 files changed, 81 insertions(+), 47 deletions(-) diff --git a/powerdnsadmin/models/setting.py b/powerdnsadmin/models/setting.py index a46cfb6..a713d76 100644 --- a/powerdnsadmin/models/setting.py +++ b/powerdnsadmin/models/setting.py @@ -110,6 +110,10 @@ class Setting(db.Model): 'oidc_oauth_email': 'email', 'oidc_oauth_account_name_property': '', 'oidc_oauth_account_description_property': '', + 'saml_autoprovisioning': False, + 'saml_urn_value': '', + 'saml_autoprovisioning_attribute': '', + 'saml_purge': False, 'forward_records_allow_edit': { 'A': True, 'AAAA': True, diff --git a/powerdnsadmin/models/user.py b/powerdnsadmin/models/user.py index f5e3556..9021e4b 100644 --- a/powerdnsadmin/models/user.py +++ b/powerdnsadmin/models/user.py @@ -659,11 +659,11 @@ 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, urn_value): """ Update user associations based on ldap attribute """ - entitlements= getCorrectEntitlements(Entitlements) + entitlements= getCorrectEntitlements(Entitlements, urn_value) if len(entitlements)!=0: self.revoke_privilege(True) for entitlement in entitlements: @@ -702,12 +702,11 @@ class User(db.Model): if account!=None: account.add_user(user) -def getCorrectEntitlements(Entitlements): +def getCorrectEntitlements(Entitlements, urn_value): """ Gather a list of valid records from the ldap attribute given """ from ..models.role import Role - urn_value=Setting().get('urn_value') 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 ccdcd6b..2fb3b4b 100644 --- a/powerdnsadmin/routes/index.py +++ b/powerdnsadmin/routes/index.py @@ -504,7 +504,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'): @@ -924,7 +924,6 @@ def saml_metadata(): resp = make_response(errors.join(', '), 500) return resp - @index_bp.route('/saml/authorized', methods=['GET', 'POST']) def saml_authorized(): errors = [] @@ -989,51 +988,77 @@ def saml_authorized(): user.firstname = name[0] user.lastname = ' '.join(name[1:]) - if group_attribute_name: - user_groups = session['samlUserdata'].get(group_attribute_name, []) - else: - user_groups = [] - if admin_attribute_name or group_attribute_name: - user_accounts = set(user.get_accounts()) - saml_accounts = [] - for group_mapping in group_to_account_mapping: - mapping = group_mapping.split('=') - group = mapping[0] - account_name = mapping[1] + if not Setting().get('saml_autoprovisioning'): + if group_attribute_name: + user_groups = session['samlUserdata'].get(group_attribute_name, []) + else: + user_groups = [] + if admin_attribute_name or group_attribute_name: + user_accounts = set(user.get_accounts()) + saml_accounts = [] + for group_mapping in group_to_account_mapping: + mapping = group_mapping.split('=') + group = mapping[0] + account_name = mapping[1] - if group in user_groups: + if group in user_groups: + account = handle_account(account_name) + saml_accounts.append(account) + + for account_name in session['samlUserdata'].get( + account_attribute_name, []): account = handle_account(account_name) saml_accounts.append(account) + saml_accounts = set(saml_accounts) + for account in saml_accounts - user_accounts: + account.add_user(user) + history = History(msg='Adding {0} to account {1}'.format( + user.username, account.name), + created_by='SAML Assertion') + history.add() + for account in user_accounts - saml_accounts: + account.remove_user(user) + history = History(msg='Removing {0} from account {1}'.format( + user.username, account.name), + created_by='SAML Assertion') + history.add() + if admin_attribute_name and 'true' in session['samlUserdata'].get( + admin_attribute_name, []): + uplift_to_admin(user) + elif admin_group_name in user_groups: + uplift_to_admin(user) + elif admin_attribute_name or group_attribute_name: + if user.role.name != 'User': + user.role_id = Role.query.filter_by(name='User').first().id + history = History(msg='Demoting {0} to user'.format( + user.username), + created_by='SAML Assertion') + history.add() + elif Setting().get('saml_autoprovisioning'): + urn_value = Setting().get('saml_urn_value') # urn_value for + key = Setting().get('saml_autoprovisioning_attribute') + Entitlements = read_saml_entitlements(urn_value, session['samlUserdata']) + if len(Entitlements)==0 and Setting().get('saml_purge'): + if user.role.name != 'User': + user.role_id = Role.query.filter_by(name='User').first().id + history = History(msg='Demoting {0} to user'.format( + user.username), + created_by='SAML Autoprovision') + history.add() + elif len(Entitlements)!=0: + if checkForPDAEntries(Entitlements, urn_value): + 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('saml_purge'): + current_app.logger.warning('Procceding to revoke every privilige from ' + user.username + '.' ) + if user.role.name != 'User': + user.role_id = Role.query.filter_by(name='User').first().id + history = History(msg='Demoting {0} to user'.format( + user.username), + created_by='SAML Autoprovision') + history.add() - for account_name in session['samlUserdata'].get( - account_attribute_name, []): - account = handle_account(account_name) - saml_accounts.append(account) - saml_accounts = set(saml_accounts) - for account in saml_accounts - user_accounts: - account.add_user(user) - history = History(msg='Adding {0} to account {1}'.format( - user.username, account.name), - created_by='SAML Assertion') - history.add() - for account in user_accounts - saml_accounts: - account.remove_user(user) - history = History(msg='Removing {0} from account {1}'.format( - user.username, account.name), - created_by='SAML Assertion') - history.add() - if admin_attribute_name and 'true' in session['samlUserdata'].get( - admin_attribute_name, []): - uplift_to_admin(user) - elif admin_group_name in user_groups: - uplift_to_admin(user) - elif admin_attribute_name or group_attribute_name: - if user.role.name != 'User': - user.role_id = Role.query.filter_by(name='User').first().id - history = History(msg='Demoting {0} to user'.format( - user.username), - created_by='SAML Assertion') - history.add() user.plain_text_password = None user.update_profile() session['authentication_type'] = 'SAML' @@ -1043,6 +1068,12 @@ def saml_authorized(): else: return render_template('errors/SAML.html', errors=errors) +def read_saml_entitlements(urn_value, saml_userdata): + Entitlements = [] + if urn_value in saml_userdata: + for k in saml_userdata[urn_value]: + Entitlements.append(k) + return Entitlements def create_group_to_account_mapping(): group_to_account_mapping_string = current_app.config.get( From 9f8ec56183820f612dfea119a5671777d6a17fcf Mon Sep 17 00:00:00 2001 From: kkmanos Date: Wed, 8 Dec 2021 14:38:30 +0200 Subject: [PATCH 07/20] added role autoprovisioning for saml --- powerdnsadmin/models/setting.py | 2 +- powerdnsadmin/routes/index.py | 20 +++++++++++--------- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/powerdnsadmin/models/setting.py b/powerdnsadmin/models/setting.py index a713d76..d73e34b 100644 --- a/powerdnsadmin/models/setting.py +++ b/powerdnsadmin/models/setting.py @@ -111,7 +111,7 @@ class Setting(db.Model): 'oidc_oauth_account_name_property': '', 'oidc_oauth_account_description_property': '', 'saml_autoprovisioning': False, - 'saml_urn_value': '', + 'saml_urn_prefix': '', 'saml_autoprovisioning_attribute': '', 'saml_purge': False, 'forward_records_allow_edit': { diff --git a/powerdnsadmin/routes/index.py b/powerdnsadmin/routes/index.py index 2fb3b4b..9586966 100644 --- a/powerdnsadmin/routes/index.py +++ b/powerdnsadmin/routes/index.py @@ -1035,9 +1035,9 @@ def saml_authorized(): created_by='SAML Assertion') history.add() elif Setting().get('saml_autoprovisioning'): - urn_value = Setting().get('saml_urn_value') # urn_value for - key = Setting().get('saml_autoprovisioning_attribute') - Entitlements = read_saml_entitlements(urn_value, session['samlUserdata']) + urn_prefix = Setting().get('saml_urn_prefix') + autoprovisioning_attribute = Setting().get('saml_autoprovisioning_attribute') + Entitlements = read_saml_entitlements(urn_prefix, autoprovisioning_attribute, session['samlUserdata']) if len(Entitlements)==0 and Setting().get('saml_purge'): if user.role.name != 'User': user.role_id = Role.query.filter_by(name='User').first().id @@ -1046,8 +1046,8 @@ def saml_authorized(): created_by='SAML Autoprovision') history.add() elif len(Entitlements)!=0: - if checkForPDAEntries(Entitlements, urn_value): - user.updateUser(Entitlements, urn_value) + if checkForPDAEntries(Entitlements, autoprovisioning_attribute): + user.updateUser(Entitlements, autoprovisioning_attribute) else: current_app.logger.warning('Not a single powerdns-admin record was found, possibly a typo in the prefix') if Setting().get('saml_purge'): @@ -1068,11 +1068,13 @@ def saml_authorized(): else: return render_template('errors/SAML.html', errors=errors) -def read_saml_entitlements(urn_value, saml_userdata): +def read_saml_entitlements(urn_prefix, autoprovisioning_attribute, saml_userdata): Entitlements = [] - if urn_value in saml_userdata: - for k in saml_userdata[urn_value]: - Entitlements.append(k) + if autoprovisioning_attribute in saml_userdata: + for k in saml_userdata[autoprovisioning_attribute]: + pref = k.split(":powerdns-admin:")[0] + if pref == urn_prefix: + Entitlements.append(k) return Entitlements def create_group_to_account_mapping(): From 92a47ce286c05e0a4cec1b0181d5101498be6aa7 Mon Sep 17 00:00:00 2001 From: kkmanos Date: Wed, 8 Dec 2021 15:35:02 +0200 Subject: [PATCH 08/20] minor changes --- powerdnsadmin/models/setting.py | 6 +++--- powerdnsadmin/services/saml.py | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/powerdnsadmin/models/setting.py b/powerdnsadmin/models/setting.py index f1185df..b1affc3 100644 --- a/powerdnsadmin/models/setting.py +++ b/powerdnsadmin/models/setting.py @@ -138,12 +138,12 @@ class Setting(db.Model): 'saml_sign_request': False, 'saml_logout': False, 'saml_logout_url': 'https://google.com', - 'saml_assertion_encrypted': True, + 'saml_assertion_encrypted': False, 'saml_digest_algorithm': 'http://www.w3.org/2000/09/xmldsig#rsa-sha1', 'saml_signature_algorithm': 'http://www.w3.org/2000/09/xmldsig#rsa-sha1', 'saml_want_assertions_signed': True, - 'saml_sign_metadata': True, - 'saml_want_message_signed': True, + 'saml_sign_metadata': False, + 'saml_want_message_signed': False, 'saml_metadata_cache_duration': 'PT5M', 'saml_metadata_valid_until': '', 'forward_records_allow_edit': { diff --git a/powerdnsadmin/services/saml.py b/powerdnsadmin/services/saml.py index 8161ad1..32fe098 100644 --- a/powerdnsadmin/services/saml.py +++ b/powerdnsadmin/services/saml.py @@ -170,12 +170,12 @@ class SAML(object): settings['debug'] = Setting().get('saml_debug') settings['security'] = {} settings['security'][ - 'digestAlgorithm'] = 'http://www.w3.org/2001/04/xmldsig-more#rsa-sha256' + 'digestAlgorithm'] = Setting().get('saml_digest_algorithm') settings['security']['metadataCacheDuration'] = None settings['security']['metadataValidUntil'] = None settings['security']['requestedAuthnContext'] = True settings['security'][ - 'signatureAlgorithm'] = 'http://www.w3.org/2001/04/xmldsig-more#rsa-sha256' + 'signatureAlgorithm'] = Setting.get('saml_signature_algorithm') settings['security']['wantAssertionsEncrypted'] = Setting().get('saml_assertion_encrypted') settings['security']['wantAttributeStatement'] = True settings['security']['wantNameId'] = True From 3fd10013ea9856f11d4605e44d82952fed7f2efa Mon Sep 17 00:00:00 2001 From: kkmanos Date: Wed, 8 Dec 2021 15:38:27 +0200 Subject: [PATCH 09/20] minimized code. test passed --- powerdnsadmin/routes/index.py | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/powerdnsadmin/routes/index.py b/powerdnsadmin/routes/index.py index 9586966..78e065d 100644 --- a/powerdnsadmin/routes/index.py +++ b/powerdnsadmin/routes/index.py @@ -1037,7 +1037,11 @@ def saml_authorized(): elif Setting().get('saml_autoprovisioning'): urn_prefix = Setting().get('saml_urn_prefix') autoprovisioning_attribute = Setting().get('saml_autoprovisioning_attribute') - Entitlements = read_saml_entitlements(urn_prefix, autoprovisioning_attribute, session['samlUserdata']) + Entitlements = [] + if autoprovisioning_attribute in session['samlUserdata']: + for k in session['samlUserdata'][autoprovisioning_attribute]: + Entitlements.append(k) + if len(Entitlements)==0 and Setting().get('saml_purge'): if user.role.name != 'User': user.role_id = Role.query.filter_by(name='User').first().id @@ -1046,8 +1050,8 @@ def saml_authorized(): created_by='SAML Autoprovision') history.add() elif len(Entitlements)!=0: - if checkForPDAEntries(Entitlements, autoprovisioning_attribute): - user.updateUser(Entitlements, autoprovisioning_attribute) + if checkForPDAEntries(Entitlements, urn_prefix): + user.updateUser(Entitlements, urn_prefix) else: current_app.logger.warning('Not a single powerdns-admin record was found, possibly a typo in the prefix') if Setting().get('saml_purge'): @@ -1068,14 +1072,6 @@ def saml_authorized(): else: return render_template('errors/SAML.html', errors=errors) -def read_saml_entitlements(urn_prefix, autoprovisioning_attribute, saml_userdata): - Entitlements = [] - if autoprovisioning_attribute in saml_userdata: - for k in saml_userdata[autoprovisioning_attribute]: - pref = k.split(":powerdns-admin:")[0] - if pref == urn_prefix: - Entitlements.append(k) - return Entitlements def create_group_to_account_mapping(): group_to_account_mapping_string = current_app.config.get( From d83a333f0b8b7a216931939ec7effe5ea3c08b9f Mon Sep 17 00:00:00 2001 From: kkmanos Date: Wed, 8 Dec 2021 16:05:42 +0200 Subject: [PATCH 10/20] testing --- powerdnsadmin/models/setting.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/powerdnsadmin/models/setting.py b/powerdnsadmin/models/setting.py index b1affc3..de3652d 100644 --- a/powerdnsadmin/models/setting.py +++ b/powerdnsadmin/models/setting.py @@ -145,7 +145,7 @@ class Setting(db.Model): 'saml_sign_metadata': False, 'saml_want_message_signed': False, 'saml_metadata_cache_duration': 'PT5M', - 'saml_metadata_valid_until': '', + 'saml_metadata_valid_until': '999999999999999999', 'forward_records_allow_edit': { 'A': True, 'AAAA': True, From 4a97766f4bd90ae0931533318bc5e3d8dc5f5302 Mon Sep 17 00:00:00 2001 From: kkmanos Date: Wed, 8 Dec 2021 16:15:59 +0200 Subject: [PATCH 11/20] local --- powerdnsadmin/models/setting.py | 18 ++++++++++-------- powerdnsadmin/services/saml.py | 2 +- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/powerdnsadmin/models/setting.py b/powerdnsadmin/models/setting.py index 1570add..f1b9651 100644 --- a/powerdnsadmin/models/setting.py +++ b/powerdnsadmin/models/setting.py @@ -112,14 +112,16 @@ class Setting(db.Model): 'oidc_oauth_account_description_property': '', 'saml_enabled': True, 'saml_debug': True, - 'saml_metadata_url': 'https://example.com/metadata.xml', + 'saml_metadata_url': 'https://md.aai.grnet.gr/aggregates/grnet-metadata.xml', 'saml_metadata_cache_lifetime': '1', 'saml_idp_sso_binding': 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect', - 'saml_idp_entity_id': 'https://idp.example.com/idp/', - 'saml_nameid_format': 'urn:oid:0.9.2342.19200300.100.1.1', + 'saml_idp_entity_id': 'https://idp.uoa.gr/idp/shibboleth', + 'saml_nameid_format': 'urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified', 'saml_sp_requested_attributes': '[ \ + {"name": "urn:oid:0.9.2342.19200300.100.1.1", "nameFormat": "urn:oasis:names:tc:SAML:2.0:attrname-format:uri", "isRequired": true, "friendlyName": "username" }, \ {"name": "urn:oid:0.9.2342.19200300.100.1.3", "nameFormat": "urn:oasis:names:tc:SAML:2.0:attrname-format:uri", "isRequired": true, "friendlyName": "email"}, \ - {"name": "mail", "isRequired": false, "friendlyName": "test-field"} \ + {"name": "urn:oid:2.5.4.42", "nameFormat": "urn:oasis:names:tc:SAML:2.0:attrname-format:uri", "isRequired": false, "friendlyName": "givenname"}, \ + {"name": "urn:oid:2.5.4.4", "nameFormat": "urn:oasis:names:tc:SAML:2.0:attrname-format:uri", "isRequired": false, "friendlyName": "surname" } \ ]', 'saml_attribute_email': 'urn:oid:0.9.2342.19200300.100.1.3', 'saml_attribute_givenname': 'urn:oid:2.5.4.42', @@ -130,13 +132,13 @@ class Setting(db.Model): 'saml_attribute_group': None, 'saml_group_admin_name': None, 'saml_group_to_account_mapping': None, - 'saml_sp_entity_id': 'http://', - 'saml_sp_contact_name': '', - 'saml_sp_contact_mail': '', + 'saml_sp_entity_id': 'https://dns.uoa.gr', + 'saml_sp_contact_name': 'admin', + 'saml_sp_contact_mail': 'pda@uoa.gr', 'saml_cert_file': '/etc/pki/powerdns-admin/cert.crt', 'saml_cert_key': '/etc/pki/powerdns-admin/key.pem', 'saml_sign_request': False, - 'saml_logout': False, + 'saml_logout': True, 'saml_logout_url': 'https://google.com', 'saml_assertion_encrypted': False, 'saml_digest_algorithm': 'http://www.w3.org/2000/09/xmldsig#rsa-sha1', diff --git a/powerdnsadmin/services/saml.py b/powerdnsadmin/services/saml.py index 32fe098..d64f2ea 100644 --- a/powerdnsadmin/services/saml.py +++ b/powerdnsadmin/services/saml.py @@ -175,7 +175,7 @@ class SAML(object): settings['security']['metadataValidUntil'] = None settings['security']['requestedAuthnContext'] = True settings['security'][ - 'signatureAlgorithm'] = Setting.get('saml_signature_algorithm') + 'signatureAlgorithm'] = Setting().get('saml_signature_algorithm') settings['security']['wantAssertionsEncrypted'] = Setting().get('saml_assertion_encrypted') settings['security']['wantAttributeStatement'] = True settings['security']['wantNameId'] = True From 1bf1d29dcc7736ed2f79850fc3a75d79898f7f31 Mon Sep 17 00:00:00 2001 From: vmarkop Date: Thu, 9 Dec 2021 13:15:45 +0200 Subject: [PATCH 12/20] Added more settings to SAML interface --- powerdnsadmin/routes/admin.py | 86 ++++-- .../admin_setting_authentication.html | 267 +++++++++++++++--- 2 files changed, 292 insertions(+), 61 deletions(-) diff --git a/powerdnsadmin/routes/admin.py b/powerdnsadmin/routes/admin.py index c223877..88d260d 100644 --- a/powerdnsadmin/routes/admin.py +++ b/powerdnsadmin/routes/admin.py @@ -1641,7 +1641,9 @@ def setting_authentication(): Setting().set('saml_metadata_url', request.form.get('saml_metadata_url')) Setting().set('saml_metadata_cache_lifetime', - request.form.get('saml_metadata_cache_lifetime')) + request.form.get('saml_metadata_cache_lifetime' \ + if request.form.get('saml_metadata_cache_lifetime') \ + else Setting().defaults['saml_metadata_cache_lifetime'])) Setting().set('saml_idp_sso_binding', request.form.get('saml_idp_sso_binding')) Setting().set('saml_idp_entity_id', @@ -1651,44 +1653,96 @@ def setting_authentication(): Setting().set('saml_sp_requested_attributes', request.form.get('saml_sp_requested_attributes')) Setting().set('saml_attribute_email', - request.form.get('saml_attribute_email')) + request.form.get('saml_attribute_email' \ + if request.form.get('saml_attribute_email') \ + else Setting().defaults['saml_attribute_email'])) Setting().set('saml_attribute_givenname', - request.form.get('saml_attribute_givenname')) + request.form.get('saml_attribute_givenname' \ + if request.form.get('saml_attribute_givenname') \ + else Setting().defaults['saml_attribute_givenname'])) Setting().set('saml_attribute_surname', - request.form.get('saml_attribute_surname')) + request.form.get('saml_attribute_surname' \ + if request.form.get('saml_attribute_surname') \ + else Setting().defaults['saml_attribute_surname'])) Setting().set('saml_attribute_username', request.form.get('saml_attribute_username')) Setting().set('saml_attribute_admin', - request.form.get('saml_attribute_admin')) + request.form.get('saml_attribute_admin' \ + if request.form.get('saml_attribute_admin') \ + else Setting().defaults['saml_attribute_admin'])) Setting().set('saml_attribute_account', - request.form.get('saml_attribute_account')) + request.form.get('saml_attribute_account' \ + if request.form.get('saml_attribute_account') \ + else Setting().defaults['saml_attribute_account'])) Setting().set('saml_sp_entity_id', request.form.get('saml_sp_entity_id')) Setting().set('saml_sp_contact_name', - request.form.get('saml_sp_contact_name')) + request.form.get('saml_sp_contact_name' \ + if request.form.get('saml_sp_contact_name') \ + else Setting().defaults['saml_sp_contact_name'])) Setting().set('saml_sp_contact_mail', - request.form.get('saml_sp_contact_mail')) + request.form.get('saml_sp_contact_mail' \ + if request.form.get('saml_sp_contact_mail') \ + else Setting().defaults['saml_sp_contact_mail'])) Setting().set('saml_cert_file', request.form.get('saml_cert_file')) Setting().set('saml_cert_key', request.form.get('saml_cert_key')) - Setting().set('saml_sign_request', - request.form.get('saml_sign_request')) - Setting().set('saml_logout', - request.form.get('saml_logout')) - Setting().set('saml_logout_url', - request.form.get('saml_logout_url')) - Setting().set('saml_assertion_encrypted', - request.form.get('saml_assertion_encrypted')) Setting().set( 'saml_sign_request', True if request.form.get('saml_sign_request') else False) Setting().set( 'saml_logout', True if request.form.get('saml_logout') else False) + if request.form.get('saml_logout_url'): + Setting().set('saml_logout_url', + request.form.get('saml_logout_url')) Setting().set( 'saml_assertion_encrypted', True if request.form.get('saml_assertion_encrypted') else False) + Setting().set( + 'saml_want_assertions_signed', + True if request.form.get('saml_want_assertions_signed') else False) + Setting().set('saml_digest_algorithm', + request.form.get('saml_digest_algorithm')) + Setting().set('saml_signature_algorithm', + request.form.get('saml_signature_algorithm')) + Setting().set( + 'saml_want_message_signed', + True if request.form.get('saml_want_message_signed') else False) + Setting().set( + 'saml_sign_metadata', + True if request.form.get('saml_sign_metadata') else False) + if request.form.get('saml_metadata_cache_duration'): + Setting().set('saml_metadata_cache_duration', + request.form.get('saml_metadata_cache_duration')) + if request.form.get('saml_metadata_valid_until'): + Setting().set('saml_metadata_valid_until', + request.form.get('saml_metadata_valid_until')) + + Setting().set( + 'saml_autoprovisioning', True + if request.form.get('saml_autoprovisioning') == 'ON' else False) + if request.form.get('autoprovisioning')=='ON': + Setting().set('saml_autoprovisioning_attribute', + request.form.get('saml_autoprovisioning_attribute')) + + if validateURN(request.form.get('saml_urn_value')): + Setting().set('saml_urn_value', + request.form.get('saml_urn_value')) + else: + return render_template('admin_setting_authentication.html', + error="Invalid urn") + else: + if request.form.get('saml_autoprovisioning_attribute'): + Setting().set('saml_autoprovisioning_attribute', + request.form.get('saml_autoprovisioning_attribute')) + if request.form.get('saml_urn_value'): + Setting().set('saml_urn_value', + request.form.get('saml_urn_value')) + Setting().set('saml_purge', True + if request.form.get('purge') == 'ON' else False) + result = { 'status': True, 'msg': diff --git a/powerdnsadmin/templates/admin_setting_authentication.html b/powerdnsadmin/templates/admin_setting_authentication.html index 1f609bd..8a650c9 100644 --- a/powerdnsadmin/templates/admin_setting_authentication.html +++ b/powerdnsadmin/templates/admin_setting_authentication.html @@ -805,7 +805,7 @@
    - ADVANCED + ENCRYPTION
    @@ -832,6 +832,9 @@
    +
    +
    + DURATION
    @@ -843,6 +846,41 @@
    +
    + AUTOPROVISION +
    +
    + +     +
    +
    + + + +
    +
    + + + {% if error %} + Please input the correct prefix for your urn value + {% endif %} +
    +
    +
    + +     +
    +
    @@ -855,10 +893,10 @@

    • - Metadata URL - + Metadata URL - URL to fetch metadata from
    • - Metadata Cache Lifetime - Cache Lifetime in Seconds + Metadata Cache Lifetime - Cache Lifetime in Seconds before fresh metadata are requested
    @@ -929,13 +967,13 @@ be created and the user added to them.
  • - SP Entity ID - + SP Entity ID - The entity ID of your Service Provider (SP).
  • - SP Entity Name - + SP Entity Name - Contact information about your SP, to be included in the generated metadata.
  • - SP Entity Mail - + SP Entity Mail - Contact information about your SP, to be included in the generated metadata.
  • @@ -961,8 +999,8 @@ Choose whether user is logged out of SAML session and possibly redirect them elsewhere.
    • - SAML Logout - Use SAML standard logout mechanism retreived from idp metadata.
      - If configured false don't care about SAML session on logout.
      + SAML Logout - Use SAML standard logout mechanism retreived from IDP metadata.
      + If disabled, don't care about SAML session on logout.
      Logout from PowerDNS-Admin only and keep SAML session authenticated.
    • @@ -970,6 +1008,75 @@
    +
    ENCRYPTION
    +

    +
      +
    • + Encrypted Assertion - Choose whether assertions are encrypted. +
    • +
    • + Want Assertions Signed - Expect incoming assertions to be signed. +
    • +
    • + Digest Algorithm - Encryption algorithm to encode outgoing and decode incoming metadata. +
    • +
    • + Signature Algorithm - Encryption algorithm to encode/decode signatures. +
    • +
    • + Want Message Signed - Expect incoming messages to be signed. +
    • +
    • + Sign Metadata - Choose whether metadata produced is signed. +
    • +
    +
    +
    DURATION
    +

    +
      +
    • + Metadata Cache Duration - Set the cache duration of generated metadata. +
    • +
    • + Metadata Valid Until - Set the expiration moment (in seconds) for generated metadata. +
    • +
    +
    +
    AUTOPROVISION
    +
    Provision PDA user privileges based on SAML Token Attributes. + This feature and the "Admin / Account" attribute assertion 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 SAML SP db every time they log in. +
    • +
    • + Roles provisioning field - The urn value of the attribute in the SAML Token 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 SAML Token is:
      +
        if PDA-Role∈[Administrator, Operator]: +
          +
        • + prefix:"powerdns-admin":PDA-Role +
        • +
        + else (if user):
        +
          +
        • + 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 SAML token. Must comply with RFC no.8141. +
    • +
    • + Purge Roles If Empty - If toggled on, SAML logins 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. +
    • +
    +
    @@ -1348,6 +1455,23 @@ //END: OIDC Tab JS // START: SAML tab js + $('#saml_autoprovisioning_on').iCheck({ + radioClass: 'iradio_square-blue', + increaseArea: '20%' + }); + $('#saml_autoprovisioning_off').iCheck({ + radioClass: 'iradio_square-blue', + increaseArea: '20%' + }); + $('#saml_purge_on').iCheck({ + radioClass: 'iradio_square-blue', + increaseArea: '20%' + }); + $('#saml_purge_off').iCheck({ + radioClass: 'iradio_square-blue', + increaseArea: '20%' + }); + $('#saml_enabled').iCheck({ checkboxClass : 'icheckbox_square-blue', increaseArea : '20%' @@ -1355,84 +1479,137 @@ var is_enabled = e.currentTarget.checked; if (is_enabled){ $('#saml_metadata_url').prop('required', true); - $('#saml_metadata_cache_lifetime').prop('required', true); + // $('#saml_metadata_cache_lifetime').prop('required', true); $('#saml_idp_sso_binding').prop('required', true); $('#saml_idp_entity_id').prop('required', true); $('#saml_nameid_format').prop('required', true); $('#saml_sp_requested_attributes').prop('required', true); - $('#saml_attribute_email').prop('required', true); - $('#saml_attribute_givenname').prop('required', true); - $('#saml_attribute_surname').prop('required', true); + // $('#saml_attribute_email').prop('required', true); + // $('#saml_attribute_givenname').prop('required', true); + // $('#saml_attribute_surname').prop('required', true); $('#saml_attribute_username').prop('required', true); - $('#saml_attribute_admin').prop('required', true); - $('#saml_attribute_account').prop('required', true); + // $('#saml_attribute_admin').prop('required', true); + // $('#saml_attribute_account').prop('required', true); $('#saml_sp_entity_id').prop('required', true); - $('#saml_sp_contact_name').prop('required', true); - $('#saml_sp_contact_mail').prop('required', true); + // $('#saml_sp_contact_name').prop('required', true); + // $('#saml_sp_contact_mail').prop('required', true); $('#saml_cert_file').prop('required', true); $('#saml_cert_key').prop('required', true); - $('#saml_logout_url').prop('required', true); $('#saml_digest_algorithm').prop('required', true); $('#saml_signature_algorithm').prop('required', true); - $('#saml_metadata_cache_duration').prop('required', true); - $('#saml_metadata_valid_until').prop('required', true); + // $('#saml_metadata_cache_duration').prop('required', true); + // $('#saml_metadata_valid_until').prop('required', true); + if ($('#saml_logout').is(":checked")) { + $('#saml_logout_url').prop('required', true); + } + if ($('#saml_autoprovisioning_on').is(":checked")) { + $('#saml_autoprovisioning_attribute').prop('required', true); + $('#saml_urn_prefix').prop('required', true); + } } else { $('#saml_metadata_url').prop('required', false); - $('#saml_metadata_cache_lifetime').prop('required', false); + // $('#saml_metadata_cache_lifetime').prop('required', false); $('#saml_idp_sso_binding').prop('required', false); $('#saml_idp_entity_id').prop('required', false); $('#saml_nameid_format').prop('required', false); $('#saml_sp_requested_attributes').prop('required', false); - $('#saml_attribute_email').prop('required', false); - $('#saml_attribute_givenname').prop('required', false); - $('#saml_attribute_surname').prop('required', false); + // $('#saml_attribute_email').prop('required', false); + // $('#saml_attribute_givenname').prop('required', false); + // $('#saml_attribute_surname').prop('required', false); $('#saml_attribute_username').prop('required', false); - $('#saml_attribute_admin').prop('required', false); - $('#saml_attribute_account').prop('required', false); + // $('#saml_attribute_admin').prop('required', false); + // $('#saml_attribute_account').prop('required', false); $('#saml_sp_entity_id').prop('required', false); - $('#saml_sp_contact_name').prop('required', false); - $('#saml_sp_contact_mail').prop('required', false); + // $('#saml_sp_contact_name').prop('required', false); + // $('#saml_sp_contact_mail').prop('required', false); $('#saml_cert_file').prop('required', false); $('#saml_cert_key').prop('required', false); $('#saml_logout_url').prop('required', false); $('#saml_digest_algorithm').prop('required', false); $('#saml_signature_algorithm').prop('required', false); - $('#saml_metadata_cache_duration').prop('required', false); - $('#saml_metadata_valid_until').prop('required', false); + // $('#saml_metadata_cache_duration').prop('required', false); + // $('#saml_metadata_valid_until').prop('required', false); + $('#saml_autoprovisioning_attribute').prop('required', false); + $('#saml_urn_prefix').prop('required', false); } }); // init validation requirement at first time page load {% if SETTING.get('saml_enabled') %} $('#saml_metadata_url').prop('required', true); - $('#saml_metadata_cache_lifetime').prop('required', true); + // $('#saml_metadata_cache_lifetime').prop('required', true); $('#saml_idp_sso_binding').prop('required', true); $('#saml_idp_entity_id').prop('required', true); $('#saml_nameid_format').prop('required', true); $('#saml_sp_requested_attributes').prop('required', true); - $('#saml_attribute_email').prop('required', true); - $('#saml_attribute_givenname').prop('required', true); - $('#saml_attribute_surname').prop('required', true); + // $('#saml_attribute_email').prop('required', true); + // $('#saml_attribute_givenname').prop('required', true); + // $('#saml_attribute_surname').prop('required', true); $('#saml_attribute_username').prop('required', true); - $('#saml_attribute_admin').prop('required', true); - $('#saml_attribute_account').prop('required', true); + // $('#saml_attribute_admin').prop('required', true); + // $('#saml_attribute_account').prop('required', true); $('#saml_sp_entity_id').prop('required', true); - $('#saml_sp_contact_name').prop('required', true); - $('#saml_sp_contact_mail').prop('required', true); - $('#saml_cert_file').prop('required', true); + // $('#saml_sp_contact_name').prop('required', true); + // $('#saml_sp_contact_mail').prop('required', true); + // $('#saml_cert_file').prop('required', true); $('#saml_cert_key').prop('required', true); - $('#saml_logout').prop('required', true); - $('#saml_logout_url').prop('required', true); $('#saml_digest_algorithm').prop('required', true); $('#saml_signature_algorithm').prop('required', true); - $('#saml_metadata_cache_duration').prop('required', true); - $('#saml_metadata_valid_until').prop('required', true); + // $('#saml_metadata_cache_duration').prop('required', true); + // $('#saml_metadata_valid_until').prop('required', true); + if ($('#saml_logout').is(":checked")) { + $('#saml_logout_url').prop('required', true); + } + if ($('#saml_autoprovisioning_on').is(":checked")) { + $('#saml_autoprovisioning_attribute').prop('required', true); + $('#saml_urn_prefix').prop('required', true); + } {% endif %} - $('#saml_sign_request').iCheck({ + $("#saml_autoprovisioning_on" ).on('ifChanged',function(){ + if ($('#saml_autoprovisioning_on').is(":checked") && $('#saml_enabled').is(":checked")) { + $('#saml_autoprovisioning_attribute').prop('required', true); + $('#saml_urn_prefix').prop('required', true); + } + else{ + $('#saml_autoprovisioning_attribute').prop('required', false); + $('#saml_urn_prefix').prop('required', false); + } + }); + + $('#saml_purge_on').on('ifClicked', function(event){ + if( !$(event.target).is(':checked')){ + var modal = $("#modal_confirm"); + 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 () { + modal.modal('hide'); + }) + modal.find('#button_cancel').click(function () { + $('#saml_purge_off').iCheck('check'); + modal.modal('hide'); + }) + modal.find('#X').click(function () { + $('#saml_purge_off').iCheck('check'); + modal.modal('hide'); + }) + modal.modal('show'); + } + }); + + $('#saml_logout').iCheck({ checkboxClass : 'icheckbox_square-blue', increaseArea : '20%' - }) - $('#saml_logout').iCheck({ + }).on('ifChanged', function(e) { + var is_enabled = e.currentTarget.checked; + if (is_enabled){ + $('#saml_logout_url').prop('required', true); + } + else{ + $('#saml_logout_url').prop('required', false); + } + }); + + $('#saml_sign_request').iCheck({ checkboxClass : 'icheckbox_square-blue', increaseArea : '20%' }) From d57a37e9c127bd8b83902ee2ad628e63049d4b71 Mon Sep 17 00:00:00 2001 From: vmarkop Date: Thu, 9 Dec 2021 17:36:19 +0200 Subject: [PATCH 13/20] Refactored and recategorized SAML Settings --- powerdnsadmin/models/setting.py | 10 +- powerdnsadmin/routes/admin.py | 19 +- powerdnsadmin/services/saml.py | 26 +- .../admin_setting_authentication.html | 225 +++++++++--------- 4 files changed, 145 insertions(+), 135 deletions(-) diff --git a/powerdnsadmin/models/setting.py b/powerdnsadmin/models/setting.py index 3264570..f8378a5 100644 --- a/powerdnsadmin/models/setting.py +++ b/powerdnsadmin/models/setting.py @@ -114,7 +114,8 @@ class Setting(db.Model): 'saml_debug': True, 'saml_metadata_url': 'https://md.aai.grnet.gr/aggregates/grnet-metadata.xml', 'saml_metadata_cache_lifetime': '1', - 'saml_idp_sso_binding': 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect', + 'saml_idp_sso_binding': 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST', + 'saml_idp_slo_binding': 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect', 'saml_idp_entity_id': 'https://idp.uoa.gr/idp/shibboleth', 'saml_nameid_format': 'urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified', 'saml_sp_requested_attributes': '[ \ @@ -137,15 +138,18 @@ class Setting(db.Model): 'saml_sp_contact_mail': 'pda@uoa.gr', 'saml_cert_file': '/etc/pki/powerdns-admin/cert.crt', 'saml_cert_key': '/etc/pki/powerdns-admin/key.pem', - 'saml_sign_request': False, + 'saml_sign_authn_request': False, + 'saml_sign_logout_request_response': False, 'saml_logout': True, 'saml_logout_url': 'https://google.com', - 'saml_assertion_encrypted': False, + 'saml_want_assertions_encrypted': False, 'saml_digest_algorithm': 'http://www.w3.org/2000/09/xmldsig#rsa-sha1', 'saml_signature_algorithm': 'http://www.w3.org/2000/09/xmldsig#rsa-sha1', 'saml_want_assertions_signed': True, 'saml_sign_metadata': False, 'saml_want_message_signed': False, + 'saml_nameid_encrypted': 'False', + 'saml_want_nameid_encrypted': 'False', 'saml_metadata_cache_duration': 'PT5M', 'saml_metadata_valid_until': '999999999999999999', 'saml_autoprovisioning': True, diff --git a/powerdnsadmin/routes/admin.py b/powerdnsadmin/routes/admin.py index 88d260d..86f068c 100644 --- a/powerdnsadmin/routes/admin.py +++ b/powerdnsadmin/routes/admin.py @@ -1646,6 +1646,8 @@ def setting_authentication(): else Setting().defaults['saml_metadata_cache_lifetime'])) Setting().set('saml_idp_sso_binding', request.form.get('saml_idp_sso_binding')) + Setting().set('saml_idp_slo_binding', + request.form.get('saml_idp_slo_binding')) Setting().set('saml_idp_entity_id', request.form.get('saml_idp_entity_id')) Setting().set('saml_nameid_format', @@ -1689,8 +1691,11 @@ def setting_authentication(): Setting().set('saml_cert_key', request.form.get('saml_cert_key')) Setting().set( - 'saml_sign_request', - True if request.form.get('saml_sign_request') else False) + 'saml_sign_authn_request', + True if request.form.get('saml_sign_authn_request') else False) + Setting().set( + 'saml_sign_logout_request_response', + True if request.form.get('saml_sign_logout_request_response') else False) Setting().set( 'saml_logout', True if request.form.get('saml_logout') else False) @@ -1698,11 +1703,17 @@ def setting_authentication(): Setting().set('saml_logout_url', request.form.get('saml_logout_url')) Setting().set( - 'saml_assertion_encrypted', - True if request.form.get('saml_assertion_encrypted') else False) + 'saml_want_assertions_encrypted', + True if request.form.get('saml_want_assertions_encrypted') else False) Setting().set( 'saml_want_assertions_signed', True if request.form.get('saml_want_assertions_signed') else False) + Setting().set( + 'saml_want_nameid_encrypted', + True if request.form.get('saml_want_nameid_encrypted') else False) + Setting().set( + 'saml_nameid_encrypted', + True if request.form.get('saml_nameid_encrypted') else False) Setting().set('saml_digest_algorithm', request.form.get('saml_digest_algorithm')) Setting().set('saml_signature_algorithm', diff --git a/powerdnsadmin/services/saml.py b/powerdnsadmin/services/saml.py index d64f2ea..3c8e658 100644 --- a/powerdnsadmin/services/saml.py +++ b/powerdnsadmin/services/saml.py @@ -44,17 +44,17 @@ class SAML(object): def get_idp_data(self): - # lifetime = timedelta( - # minutes=int(Setting().get('saml_metadata_cache_lifetime'))) # should be seconds instead of minutes? + lifetime = timedelta( + minutes=int(Setting().get('saml_metadata_cache_lifetime'))) # should be seconds instead of minutes? # Since SAML is now user-configurable, idp_data may change before the lifetime has ended, # so metadata should not be cached at all, or outdated settings may be used. try: self.retrieve_idp_data() except: return None - # if self.idp_timestamp + lifetime < datetime.now(): - background_thread = Thread(target=self.retrieve_idp_data()) - background_thread.start() + if self.idp_timestamp + lifetime < datetime.now(): + background_thread = Thread(target=self.retrieve_idp_data()) + background_thread.start() return self.idp_data @@ -158,12 +158,12 @@ class SAML(object): settings['sp']['assertionConsumerService'] = {} settings['sp']['assertionConsumerService'][ - 'binding'] = 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST' + 'binding'] = Setting().get('saml_idp_sso_binding') settings['sp']['assertionConsumerService'][ 'url'] = own_url + '/saml/authorized' settings['sp']['singleLogoutService'] = {} settings['sp']['singleLogoutService'][ - 'binding'] = 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect' + 'binding'] = Setting().get('saml_idp_slo_binding') settings['sp']['singleLogoutService']['url'] = own_url + '/saml/sls' settings['idp'] = metadata['idp'] settings['strict'] = True @@ -176,17 +176,17 @@ class SAML(object): settings['security']['requestedAuthnContext'] = True settings['security'][ 'signatureAlgorithm'] = Setting().get('saml_signature_algorithm') - settings['security']['wantAssertionsEncrypted'] = Setting().get('saml_assertion_encrypted') + settings['security']['wantAssertionsEncrypted'] = Setting().get('saml_want_assertions_encrypted') settings['security']['wantAttributeStatement'] = True settings['security']['wantNameId'] = True - settings['security']['authnRequestsSigned'] = Setting().get('saml_sign_request') - settings['security']['logoutRequestSigned'] = Setting().get('saml_sign_request') - settings['security']['logoutResponseSigned'] = Setting().get('saml_sign_request') - settings['security']['nameIdEncrypted'] = False + settings['security']['authnRequestsSigned'] = Setting().get('saml_sign_authn_request') + settings['security']['logoutRequestSigned'] = Setting().get('saml_sign_logout_request_response') + settings['security']['logoutResponseSigned'] = Setting().get('saml_sign_logout_request_response') + settings['security']['nameIdEncrypted'] = Setting().get('saml_nameid_encrypted') settings['security']['signMetadata'] = Setting().get('saml_sign_metadata') settings['security']['wantAssertionsSigned'] = Setting().get('saml_want_assertions_signed') settings['security']['wantMessagesSigned'] = Setting().get('saml_want_message_signed') - settings['security']['wantNameIdEncrypted'] = False + settings['security']['wantNameIdEncrypted'] = Setting().get('saml_want_nameid_encrypted') settings['contactPerson'] = {} settings['contactPerson']['support'] = {} settings['contactPerson']['support']['emailAddress'] = Setting().get('saml_sp_contact_mail') diff --git a/powerdnsadmin/templates/admin_setting_authentication.html b/powerdnsadmin/templates/admin_setting_authentication.html index 8a650c9..044c588 100644 --- a/powerdnsadmin/templates/admin_setting_authentication.html +++ b/powerdnsadmin/templates/admin_setting_authentication.html @@ -691,39 +691,63 @@ -
    - METADATA -
    - - - -
    -
    - - - -
    -
    IDP -
    - - - -
    - - + + + +
    +
    + + + +
    +
    + + + +
    +
    + +
    - ATTRIBUTES + SP +
    + + + +
    +
    + + + +
    +
    + + + +
    +
    + + + +
    +
    + + +
    +
    +
    + SP ATTRIBUTES
    @@ -749,21 +773,6 @@
    -
    - - - -
    -
    - - - -
    -
    - - - -
    @@ -776,7 +785,7 @@
    - CERTIFICATE + SIGNING & ENCRYPTION
    @@ -788,8 +797,42 @@
    - - + + +
    +
    + + +
    +
    + + +
    +
    + + +
    +
    + + +
    +
    + + +
    +
    + + +
    +
    + + + +
    +
    + + +
    @@ -804,50 +847,18 @@
    -
    - ENCRYPTION -
    - - -
    -
    - - -
    -
    - - - -
    -
    - - - -
    -
    - - -
    -
    - - -
    -
    -
    - DURATION -
    - - - -
    -
    - - - -
    -
    AUTOPROVISION +
    + + + +
    +
    + + + +

    - +
    @@ -818,11 +818,11 @@
    - +
    - +
    @@ -900,32 +900,97 @@
    Help
    -
    METADATA
    +
    General

    + Enable SAML - Enables or disables SAML Authentication.
    + If toggled on, the following fields must be filled in the left form:
    • - Metadata URL - URL to fetch metadata from + IDP Entity ID
    • - Metadata Cache Lifetime - Cache Lifetime in minutes before fresh metadata are requested + IDP Metadata URL +
    • +
    • + IDP SSO BINDING +
    • +
    • + IDP SLO BINDING +
    • +
    • + SP Entity ID +
    • +
    • + SP NameID Format +
    • +
    • + SP Requested Attributes +
    • +
    • + Cert File +
    • +
    • + Cert Key +
    • +
    • + Digest Algorithm +
    • +
    • + Signature Algorithm +
    • +
    • + Logout URL, if SAML Logout is toggled on +
    • +
    • + Roles Provisioning Field and Urn Prefix, if Roles Autoprovisioning is toggled on.
    + The rest can be filled, or left empty to revert to the Default settings.
    IDP

      +
    • + IDP Entity ID - EntityID of the IDP to use. Only needed if more than one IDP is in the SAML_METADATA_URL +
    • +
    • + IDP Metadata URL - URL to fetch IDP metadata from +
    • +
    • + IDP Metadata Cache Lifetime - Cache Lifetime in minutes before fresh metadata are requested from the IDP Metadata URL +
    • IDP SSO Binding - SAML SSO binding format to use
    • - IDP Entity ID - EntityID of the IdP to use. Only needed if more than one IdP is in the SAML_METADATA_URL + IDP SLO Binding - SAML SLO binding format to use
    • NameID Format - NameID format to request
    -
    ATTRIBUTES
    +
    SP
    +

    +
      +
    • + SP Entity ID - The entity ID of your Service Provider (SP). +
    • +
    • + SP NameID Format - NameID format to request +
    • +
    • + SP Metadata Cache Duration - Set the cache duration of generated metadata. +
    • +
    • + SP Metadata Valid Until - Set the expiration moment (in seconds) for generated metadata. +
    • +
    • + Sign SP Metadata - Choose whether metadata produced is signed. +
    • +
    +
    +
    SP ATTRIBUTES

    • @@ -965,6 +1030,79 @@
    • Username - Attribute to use for username.
    • +
    • + SP Entity Name - Contact information about your SP, to be included in the generated metadata. +
    • +
    • + SP Entity Mail - Contact information about your SP, to be included in the generated metadata. +
    • +
    +
    +
    ENCRYPTION
    +

    +
      +
    • + The Cert File - Cert Key pair configures the path + to certificate file and it's respective private key file. + It is used for signing metadata, encrypting tokens and all other + signing/encryption tasks during communication between iDP and SP.
      + NOTE: If these two parameters aren't explicitly provided, a self-signed certificate-key pair + will be generated in "PowerDNS-Admin" root directory.
      + CAUTION: For production use, usage of self-signed certificates is highly discouraged. + Use certificates from trusted CA instead. +
    • +
    • + Sign Authentication Request - Configures if the SP should sign outgoing authentication requests. +
    • +
    • + Sign Logout Request & Response - Configures if the SP should sign outgoing Logout requests & Logout responses. +
    • +
    • + Want Assertions Encrypted - Choose whether the SP expects assertions received from the IDP to be encrypted. +
    • +
    • + Want Assertions Signed - Choose whether the SP expects incoming assertions to be signed. +
    • +
    • + NameID Encrypted - Indicates that the nameID of the logoutRequest sent by this SP will be encrypted. +
    • +
    • + Want NameID Encrypted - Indicates a requirement for the NameID received by this SP to be encrypted. +
    • +
    • + Want Message Signed - Choose whether the SP expects incoming messages to be signed. +
    • +
    • + Digest Algorithm - Encryption algorithm to encode outgoing and decode incoming metadata. +
    • +
    • + Signature Algorithm - Encryption algorithm to encode/decode signatures. +
    • +
    +
    +
    LOGOUT
    +

    +
      +
    • + SAML Logout - Choose whether user is logged out of SAML session and possibly redirect them elsewhere. +
        +
      • + If enabled, use SAML standard logout mechanism retreived from IDP metadata. +
      • +
      • + If disabled, don't care about SAML session on logout.
        + Logout from PowerDNS-Admin only and keep SAML session authenticated. +
      • +
      +
    • +
    • + Logout URL - Configure to redirect to a url different than PowerDNS-Admin login after a successful SAML logout. +
    • +
    +
    +
    AUTOPROVISION
    +
    Provision PDA user privileges based on SAML Attributes. +
    • Admin - Attribute to get admin status from.
      If set, look for the value 'true' to set a user as an administrator.
      @@ -978,87 +1116,9 @@ be created and the user added to them.
    • - SP Entity ID - The entity ID of your Service Provider (SP). -
    • -
    • - SP Entity Name - Contact information about your SP, to be included in the generated metadata. -
    • -
    • - SP Entity Mail - Contact information about your SP, to be included in the generated metadata. -
    • -
    -
    -
    CERTIFICATE
    -
    Configures the path to certificate file and it's respective private key file. -
      -
    • - The Cert File - Cert Key pair is used for signing metadata, encrypting tokens and all other signing/encryption - tasks during communication between iDP and SP.
      - NOTE: If these two parameters aren't explicitly provided, a self-signed certificate-key pair - will be generated in "PowerDNS-Admin" root directory.
      - CAUTION: For production use, usage of self-signed certificates is highly discouraged. - Use certificates from trusted CA instead. -
    • -
    • - Sign Request - Configures if SAML tokens should be encrypted.
      - If enabled a new app certificate will be generated on restart. -
    • -
    -
    -
    LOGOUT
    -
    - Choose whether user is logged out of SAML session and possibly redirect them elsewhere. -
      -
    • - SAML Logout - Use SAML standard logout mechanism retreived from IDP metadata.
      - If disabled, don't care about SAML session on logout.
      - Logout from PowerDNS-Admin only and keep SAML session authenticated. -
    • -
    • - Logout URL - Configure to redirect to a url different than PowerDNS-Admin login after a successful SAML logout. -
    • -
    -
    -
    ENCRYPTION
    -

    -
      -
    • - Encrypted Assertion - Choose whether assertions are encrypted. -
    • -
    • - Want Assertions Signed - Expect incoming assertions to be signed. -
    • -
    • - Digest Algorithm - Encryption algorithm to encode outgoing and decode incoming metadata. -
    • -
    • - Signature Algorithm - Encryption algorithm to encode/decode signatures. -
    • -
    • - Want Message Signed - Expect incoming messages to be signed. -
    • -
    • - Sign Metadata - Choose whether metadata produced is signed. -
    • -
    -
    -
    DURATION
    -

    -
      -
    • - Metadata Cache Duration - Set the cache duration of generated metadata. -
    • -
    • - Metadata Valid Until - Set the expiration moment (in seconds) for generated metadata. -
    • -
    -
    -
    AUTOPROVISION
    -
    Provision PDA user privileges based on SAML Token Attributes. - This feature and the "Admin / Account" attribute assertion 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 SAML SP db every time they log in. + Roles Autoprovisioning - If toggled on, the PDA Role and the associations of users found in the local db, will be instantly updated from the SAML SP db every time they log in.
      + NOTE:This feature and the assertion of "Admin / Account" attributes are mutually exclusive.
      + If used, the values for Admin/Account given above will be ignored.
    • Roles provisioning field - The urn value of the attribute in the SAML Token where PDA will look for a new Role and/or new associations to domains/accounts. @@ -1083,8 +1143,9 @@
    • Purge Roles If Empty - If toggled on, SAML logins 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. + 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.
      + CAUTION: Enabling this feature will revoke existing users' access to their associated domains unless they have their autoprovisioning field prepopulated.
    diff --git a/powerdnsadmin/templates/errors/SAML.html b/powerdnsadmin/templates/errors/SAML.html index 2cfacbd..c0ebf5a 100644 --- a/powerdnsadmin/templates/errors/SAML.html +++ b/powerdnsadmin/templates/errors/SAML.html @@ -26,14 +26,15 @@ Oops! Something went wrong

    - Login failed.
    - Error(s) when processing SAML Response:
    + Login failed. + {% if error %} +
    Error(s) when processing SAML Response:

      {% for error in errors %}
    • {{ error }}
    • {% endfor %}
    - + {% endif %} You may return to the dashboard.

    From 10049fa431c619092badeb01e2680eb6c41d67d0 Mon Sep 17 00:00:00 2001 From: vmarkop Date: Tue, 14 Dec 2021 11:28:49 +0200 Subject: [PATCH 15/20] adjusting saml binding settings --- powerdnsadmin/models/setting.py | 13 ++++++---- powerdnsadmin/routes/admin.py | 2 -- powerdnsadmin/services/saml.py | 18 ++++++++----- .../admin_setting_authentication.html | 26 +++++-------------- 4 files changed, 26 insertions(+), 33 deletions(-) diff --git a/powerdnsadmin/models/setting.py b/powerdnsadmin/models/setting.py index f8378a5..f844e59 100644 --- a/powerdnsadmin/models/setting.py +++ b/powerdnsadmin/models/setting.py @@ -113,9 +113,11 @@ class Setting(db.Model): 'saml_enabled': True, 'saml_debug': True, 'saml_metadata_url': 'https://md.aai.grnet.gr/aggregates/grnet-metadata.xml', - 'saml_metadata_cache_lifetime': '1', - 'saml_idp_sso_binding': 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST', + 'saml_metadata_cache_lifetime': '15', + 'saml_idp_sso_binding': 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect', 'saml_idp_slo_binding': 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect', + 'saml_sp_acs_binding': 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST', + 'saml_sp_sls_binding': 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect', 'saml_idp_entity_id': 'https://idp.uoa.gr/idp/shibboleth', 'saml_nameid_format': 'urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified', 'saml_sp_requested_attributes': '[ \ @@ -130,6 +132,7 @@ class Setting(db.Model): 'saml_attribute_username': 'urn:oid:0.9.2342.19200300.100.1.1', 'saml_attribute_admin': 'https://example.edu/pdns-admin', 'saml_attribute_account': 'https://example.edu/pdns-account', + 'saml_attribute_name': None, 'saml_attribute_group': None, 'saml_group_admin_name': None, 'saml_group_to_account_mapping': None, @@ -148,10 +151,10 @@ class Setting(db.Model): 'saml_want_assertions_signed': True, 'saml_sign_metadata': False, 'saml_want_message_signed': False, - 'saml_nameid_encrypted': 'False', - 'saml_want_nameid_encrypted': 'False', + 'saml_nameid_encrypted': False, + 'saml_want_nameid_encrypted': False, 'saml_metadata_cache_duration': 'PT5M', - 'saml_metadata_valid_until': '999999999999999999', + 'saml_metadata_valid_until': '2021-12-31T00:00:00Z', 'saml_autoprovisioning': True, 'saml_urn_prefix': 'urn:mace:uoa.gr', 'saml_autoprovisioning_attribute': 'urn:oid:1.3.6.1.4.1.5923.1.1.1.7', diff --git a/powerdnsadmin/routes/admin.py b/powerdnsadmin/routes/admin.py index 70fa563..a461671 100644 --- a/powerdnsadmin/routes/admin.py +++ b/powerdnsadmin/routes/admin.py @@ -1646,8 +1646,6 @@ def setting_authentication(): else Setting().defaults['saml_metadata_cache_lifetime'])) Setting().set('saml_idp_sso_binding', request.form.get('saml_idp_sso_binding')) - Setting().set('saml_idp_slo_binding', - request.form.get('saml_idp_slo_binding')) Setting().set('saml_idp_entity_id', request.form.get('saml_idp_entity_id')) Setting().set('saml_nameid_format', diff --git a/powerdnsadmin/services/saml.py b/powerdnsadmin/services/saml.py index 22d8f1d..4ada154 100644 --- a/powerdnsadmin/services/saml.py +++ b/powerdnsadmin/services/saml.py @@ -8,7 +8,12 @@ from ..lib.certutil import KEY_FILE, CERT_FILE, create_self_signed_cert from ..lib.utils import urlparse from ..models.setting import Setting - +# The python3-saml library currently supports only the Redirect binding for IDP endpoints. +# For SP, the Assertion Consumer Service endpoint supports HTTP-POST binding, +# while the Single Logout Service endpoint uses HTTP-Redirect. +# Therefore, to protect users from using unsupported features, settings +# 'saml_idp_slo_binding', 'saml_sp_acs_binding' and 'saml_sp_sls_binding' +# are not exposed on the front end SAML interface. class SAML(object): def __init__(self): if Setting().get('saml_enabled'): @@ -25,8 +30,7 @@ class SAML(object): self.idp_data = OneLogin_Saml2_IdPMetadataParser.parse_remote( Setting().get('saml_metadata_url'), entity_id=Setting().get('saml_idp_entity_id'), - required_sso_binding=Setting().get('saml_idp_sso_binding'), - required_slo_binding=Setting().get('saml_idp_slo_binding')) + required_sso_binding=Setting().get('saml_idp_sso_binding')) except: self.idp_data = None else: @@ -154,12 +158,12 @@ class SAML(object): settings['sp']['assertionConsumerService'] = {} settings['sp']['assertionConsumerService'][ - 'binding'] = Setting().get('saml_idp_sso_binding') + 'binding'] = Setting().get('saml_sp_acs_binding')#'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST' settings['sp']['assertionConsumerService'][ 'url'] = own_url + '/saml/authorized' settings['sp']['singleLogoutService'] = {} settings['sp']['singleLogoutService'][ - 'binding'] = Setting().get('saml_idp_slo_binding') + 'binding'] = Setting().get('saml_sp_sls_binding')#'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect' settings['sp']['singleLogoutService']['url'] = own_url + '/saml/sls' if metadata is not None and 'idp' in metadata: settings['idp'] = metadata['idp'] @@ -168,8 +172,8 @@ class SAML(object): settings['security'] = {} settings['security'][ 'digestAlgorithm'] = Setting().get('saml_digest_algorithm') - settings['security']['metadataCacheDuration'] = None - settings['security']['metadataValidUntil'] = None + settings['security']['metadataCacheDuration'] = Setting().get('saml_metadata_cache_duration') if Setting().get('saml_metadata_cache_duration') else None + settings['security']['metadataValidUntil'] = Setting().get('saml_metadata_valid_until') if Setting().get('saml_metadata_valid_until') else None settings['security']['requestedAuthnContext'] = True settings['security'][ 'signatureAlgorithm'] = Setting().get('saml_signature_algorithm') diff --git a/powerdnsadmin/templates/admin_setting_authentication.html b/powerdnsadmin/templates/admin_setting_authentication.html index 210a50b..ea581ee 100644 --- a/powerdnsadmin/templates/admin_setting_authentication.html +++ b/powerdnsadmin/templates/admin_setting_authentication.html @@ -713,11 +713,6 @@
    -
    - - - -
    SP @@ -733,12 +728,12 @@
    - +
    - +
    @@ -914,9 +909,6 @@
  • IDP SSO BINDING
  • -
  • - IDP SLO BINDING -
  • SP Entity ID
  • @@ -960,10 +952,7 @@ IDP Metadata Cache Lifetime - Cache Lifetime in minutes before fresh metadata are requested from the IDP Metadata URL
  • - IDP SSO Binding - SAML SSO binding format to use -
  • -
  • - IDP SLO Binding - SAML SLO binding format to use + IDP SSO Binding - SAML SSO binding format required for the IDP to use
  • NameID Format - NameID format to request @@ -980,10 +969,12 @@ SP NameID Format - NameID format to request
  • - SP Metadata Cache Duration - Set the cache duration of generated metadata. + SP Metadata Cache Duration - Set the cache duration of generated metadata.
    + Use PT5M to set cache duration to 5 minutes.
  • - SP Metadata Valid Until - Set the expiration moment (in seconds) for generated metadata. + SP Metadata Valid Until - Set the expiration date, in XML DateTime String format, for generated metadata.
    + XML DateTime String Format: "YYYY-MM-DDThh:mm:ssZ", Z can be Z for timezone 0 or "+-hh:mm" for other timezones.
  • Sign SP Metadata - Choose whether metadata produced is signed. @@ -1552,7 +1543,6 @@ if (is_enabled){ $('#saml_metadata_url').prop('required', true); $('#saml_idp_sso_binding').prop('required', true); - $('#saml_idp_slo_binding').prop('required', true); $('#saml_idp_entity_id').prop('required', true); $('#saml_nameid_format').prop('required', true); $('#saml_sp_requested_attributes').prop('required', true); @@ -1572,7 +1562,6 @@ } else { $('#saml_metadata_url').prop('required', false); $('#saml_idp_sso_binding').prop('required', false); - $('#saml_idp_slo_binding').prop('required', false); $('#saml_idp_entity_id').prop('required', false); $('#saml_nameid_format').prop('required', false); $('#saml_sp_requested_attributes').prop('required', false); @@ -1591,7 +1580,6 @@ {% if SETTING.get('saml_enabled') %} $('#saml_metadata_url').prop('required', true); $('#saml_idp_sso_binding').prop('required', true); - $('#saml_idp_slo_binding').prop('required', true); $('#saml_idp_entity_id').prop('required', true); $('#saml_nameid_format').prop('required', true); $('#saml_sp_requested_attributes').prop('required', true); From 3d839c92d21ac4cf2e30ecb5391a97ea3251c5d4 Mon Sep 17 00:00:00 2001 From: vmarkop Date: Wed, 15 Dec 2021 14:45:06 +0200 Subject: [PATCH 16/20] Improved SAML Settings Help --- powerdnsadmin/models/setting.py | 2 +- powerdnsadmin/routes/admin.py | 64 ++++--- powerdnsadmin/routes/index.py | 1 + powerdnsadmin/services/saml.py | 2 +- .../admin_setting_authentication.html | 168 +++++++++++++----- 5 files changed, 162 insertions(+), 75 deletions(-) diff --git a/powerdnsadmin/models/setting.py b/powerdnsadmin/models/setting.py index f844e59..d1a1447 100644 --- a/powerdnsadmin/models/setting.py +++ b/powerdnsadmin/models/setting.py @@ -138,7 +138,7 @@ class Setting(db.Model): 'saml_group_to_account_mapping': None, 'saml_sp_entity_id': 'https://dns.uoa.gr', 'saml_sp_contact_name': 'admin', - 'saml_sp_contact_mail': 'pda@uoa.gr', + 'saml_sp_contact_mail': 'powerdnsadmin@organization.com', 'saml_cert_file': '/etc/pki/powerdns-admin/cert.crt', 'saml_cert_key': '/etc/pki/powerdns-admin/key.pem', 'saml_sign_authn_request': False, diff --git a/powerdnsadmin/routes/admin.py b/powerdnsadmin/routes/admin.py index a461671..ab5d3e2 100644 --- a/powerdnsadmin/routes/admin.py +++ b/powerdnsadmin/routes/admin.py @@ -1640,50 +1640,52 @@ def setting_authentication(): True if request.form.get('saml_enabled') else False) Setting().set('saml_metadata_url', request.form.get('saml_metadata_url')) - Setting().set('saml_metadata_cache_lifetime', - request.form.get('saml_metadata_cache_lifetime' \ - if request.form.get('saml_metadata_cache_lifetime') \ - else Setting().defaults['saml_metadata_cache_lifetime'])) + if request.form.get('saml_metadata_cache_lifetime'): + Setting().set('saml_metadata_cache_lifetime', + request.form.get('saml_metadata_cache_lifetime')) + else: + Setting().set('saml_metadata_cache_lifetime', + Setting().defaults['saml_metadata_cache_lifetime']) Setting().set('saml_idp_sso_binding', request.form.get('saml_idp_sso_binding')) + Setting().set('saml_idp_slo_binding', + request.form.get('saml_idp_slo_binding')) Setting().set('saml_idp_entity_id', request.form.get('saml_idp_entity_id')) Setting().set('saml_nameid_format', request.form.get('saml_nameid_format')) + Setting().set('saml_sp_acs_binding', + request.form.get('saml_sp_acs_binding')) + Setting().set('saml_sp_sls_binding', + request.form.get('saml_sp_sls_binding')) Setting().set('saml_sp_requested_attributes', request.form.get('saml_sp_requested_attributes')) Setting().set('saml_attribute_email', - request.form.get('saml_attribute_email' \ - if request.form.get('saml_attribute_email') \ - else Setting().defaults['saml_attribute_email'])) + request.form.get('saml_attribute_email')) Setting().set('saml_attribute_givenname', - request.form.get('saml_attribute_givenname' \ - if request.form.get('saml_attribute_givenname') \ - else Setting().defaults['saml_attribute_givenname'])) + request.form.get('saml_attribute_givenname')) Setting().set('saml_attribute_surname', - request.form.get('saml_attribute_surname' \ - if request.form.get('saml_attribute_surname') \ - else Setting().defaults['saml_attribute_surname'])) + request.form.get('saml_attribute_surname')) Setting().set('saml_attribute_username', request.form.get('saml_attribute_username')) Setting().set('saml_attribute_admin', - request.form.get('saml_attribute_admin' \ - if request.form.get('saml_attribute_admin') \ - else Setting().defaults['saml_attribute_admin'])) + request.form.get('saml_attribute_admin')) Setting().set('saml_attribute_account', - request.form.get('saml_attribute_account' \ - if request.form.get('saml_attribute_account') \ - else Setting().defaults['saml_attribute_account'])) + request.form.get('saml_attribute_account')) Setting().set('saml_sp_entity_id', request.form.get('saml_sp_entity_id')) - Setting().set('saml_sp_contact_name', - request.form.get('saml_sp_contact_name' \ - if request.form.get('saml_sp_contact_name') \ - else Setting().defaults['saml_sp_contact_name'])) - Setting().set('saml_sp_contact_mail', - request.form.get('saml_sp_contact_mail' \ - if request.form.get('saml_sp_contact_mail') \ - else Setting().defaults['saml_sp_contact_mail'])) + if request.form.get('saml_sp_contact_name'): + Setting().set('saml_sp_contact_name', + request.form.get('saml_sp_contact_name')) + else: + Setting().set('saml_sp_contact_name', + Setting().defaults['saml_sp_contact_name']) + if request.form.get('saml_sp_contact_mail'): + Setting().set('saml_sp_contact_mail', + request.form.get('saml_sp_contact_mail')) + else: + Setting().set('saml_sp_contact_mail', + Setting().defaults['saml_sp_contact_mail']) Setting().set('saml_cert_file', request.form.get('saml_cert_file')) Setting().set('saml_cert_key', @@ -1722,11 +1724,9 @@ def setting_authentication(): Setting().set( 'saml_sign_metadata', True if request.form.get('saml_sign_metadata') else False) - if request.form.get('saml_metadata_cache_duration'): - Setting().set('saml_metadata_cache_duration', + Setting().set('saml_metadata_cache_duration', request.form.get('saml_metadata_cache_duration')) - if request.form.get('saml_metadata_valid_until'): - Setting().set('saml_metadata_valid_until', + Setting().set('saml_metadata_valid_until', request.form.get('saml_metadata_valid_until')) Setting().set( @@ -1757,8 +1757,6 @@ def setting_authentication(): 'msg': 'Saved successfully. Please reload PDA to take effect.' } - # # Attempt to reinitialize SAML. If attempt fails, setting will be automatically disabled. - # SAML() else: return abort(400) diff --git a/powerdnsadmin/routes/index.py b/powerdnsadmin/routes/index.py index 32534c8..1743a05 100644 --- a/powerdnsadmin/routes/index.py +++ b/powerdnsadmin/routes/index.py @@ -1157,6 +1157,7 @@ def uplift_to_admin(user): @index_bp.route('/saml/sls') +@login_required def saml_logout(): if not Setting().get('saml_enabled'): current_app.logger.error("SAML authentication is disabled.") diff --git a/powerdnsadmin/services/saml.py b/powerdnsadmin/services/saml.py index 4ada154..9d3b615 100644 --- a/powerdnsadmin/services/saml.py +++ b/powerdnsadmin/services/saml.py @@ -12,7 +12,7 @@ from ..models.setting import Setting # For SP, the Assertion Consumer Service endpoint supports HTTP-POST binding, # while the Single Logout Service endpoint uses HTTP-Redirect. # Therefore, to protect users from using unsupported features, settings -# 'saml_idp_slo_binding', 'saml_sp_acs_binding' and 'saml_sp_sls_binding' +# 'saml_idp_sso_binding', 'saml_idp_slo_binding', 'saml_sp_acs_binding' and 'saml_sp_sls_binding' # are not exposed on the front end SAML interface. class SAML(object): def __init__(self): diff --git a/powerdnsadmin/templates/admin_setting_authentication.html b/powerdnsadmin/templates/admin_setting_authentication.html index ea581ee..133dad9 100644 --- a/powerdnsadmin/templates/admin_setting_authentication.html +++ b/powerdnsadmin/templates/admin_setting_authentication.html @@ -710,7 +710,12 @@
  • - + + +
    +
    + +
    @@ -740,6 +745,16 @@ +
    + + + +
    +
    + + + +
    SP ATTRIBUTES @@ -906,9 +921,6 @@
  • IDP Metadata URL
  • -
  • - IDP SSO BINDING -
  • SP Entity ID
  • @@ -918,6 +930,9 @@
  • SP Requested Attributes
  • +
  • + SP Username Attribute +
  • Cert File
  • @@ -943,30 +958,32 @@

    • - IDP Entity ID - EntityID of the IDP to use. Only needed if more than one IDP is in the SAML_METADATA_URL + IDP Entity ID - Specify the EntityID of the IDP to use.
      + Only needed if more the XML provided in the SAML_METADATA_URL contains more than 1 IDP Entity.
    • - IDP Metadata URL - URL to fetch IDP metadata from + IDP Metadata URL - Url where the XML of the Identity Provider Metadata is published.
    • - IDP Metadata Cache Lifetime - Cache Lifetime in minutes before fresh metadata are requested from the IDP Metadata URL + IDP Metadata Cache Lifetime - Cache Lifetime in minutes before fresh metadata are fetched from the IDP Metadata URL
    • - IDP SSO Binding - SAML SSO binding format required for the IDP to use + IDP SSO Binding - SAML SSO binding format required for the IDP to use
    • - NameID Format - NameID format to request + IDP SLO Binding - SAML SLO binding format required for the IDP to use
    • + NOTE::The Binding settings are currently disabled, as the underlying saml library currently supports only the Redirect binding for IDP endpoints.
    SP

    • - SP Entity ID - The entity ID of your Service Provider (SP). + SP Entity ID - Specify the EntityID of your Service Provider (SP).
    • - SP NameID Format - NameID format to request + SP NameID Format - NameID format to request. This specifies the content of the NameID and any associated processing rules.
    • SP Metadata Cache Duration - Set the cache duration of generated metadata.
      @@ -979,15 +996,23 @@
    • Sign SP Metadata - Choose whether metadata produced is signed.
    • +
    • + SP ACS Binding - SAML Assertion Consumer Service Binding Format for the SP to use on login. +
    • +
    • + SP SLS Binding - SAML Single Logout Service Binding Format for the SP to use on logout. +
    • + NOTE::The Binding settings are currently disabled, as in the underlying saml library, the ACS endpoint currently supports + only the HTTP-POST binding, while the SLS endpoint supports only HTTP-Redirect.
    SP ATTRIBUTES

    • - Requested Attributes - Following parameter defines RequestedAttributes section in SAML metadata - since certain iDPs require explicit attribute request. If not provided section - will not be available in metadata. + Requested Attributes - The following parameter defines RequestedAttributes section in SAML metadata + since certain IDPs require explicitly requesting attributes.
      + If not provided, the Attribute Consuming Service Section will not be available in metadata.
      Possible attributes:
      name (mandatory), nameFormat, isRequired, friendlyName @@ -1009,6 +1034,9 @@ <md:RequestedAttribute Name="mail" FriendlyName="test-field"/>
      </md:AttributeConsumingService>
    • +
    + The following attribute values must be derived from Requested Attributes, and must be in the form of a valid URN (e.g. urn:oid:2.5.4.4): +
    • Email - Attribute to use for Email address.
    • @@ -1021,6 +1049,9 @@
    • Username - Attribute to use for username.
    • +
    + These may be generic strings containing your information: +
    • SP Entity Name - Contact information about your SP, to be included in the generated metadata.
    • @@ -1034,40 +1065,40 @@
      • The Cert File - Cert Key pair configures the path - to certificate file and it's respective private key file. + to certificate file and it's respective private key file.
        It is used for signing metadata, encrypting tokens and all other - signing/encryption tasks during communication between iDP and SP.
        - NOTE: If these two parameters aren't explicitly provided, a self-signed certificate-key pair - will be generated in "PowerDNS-Admin" root directory.
        - CAUTION: For production use, usage of self-signed certificates is highly discouraged. - Use certificates from trusted CA instead. + signing/encryption tasks during communication between IDP and SP.
        + NOTE: If these two parameters aren't explicitly provided, + a self-signed certificate-key pair will be generated.
        + CAUTION: For production use, usage of self-signed certificates + is highly discouraged. Use certificates from trusted CA instead.
      • - Sign Authentication Request - Configures if the SP should sign outgoing authentication requests. + Sign Authentication Request - Configures if the SP should sign outgoing authentication requests.
      • - Sign Logout Request & Response - Configures if the SP should sign outgoing Logout requests & Logout responses. + Sign Logout Request & Response - Configures if the SP should sign outgoing Logout requests & Logout responses.
      • - Want Assertions Encrypted - Choose whether the SP expects assertions received from the IDP to be encrypted. + Want Assertions Encrypted - Choose whether the SP expects incoming assertions received from the IDP to be encrypted.
      • - Want Assertions Signed - Choose whether the SP expects incoming assertions to be signed. + Want Assertions Signed - Choose whether the SP expects incoming assertions to be signed.
      • - NameID Encrypted - Indicates that the nameID of the logoutRequest sent by this SP will be encrypted. + NameID Encrypted - Indicates that the outgoing nameID of the logoutRequest sent by this SP will be encrypted.
      • - Want NameID Encrypted - Indicates a requirement for the NameID received by this SP to be encrypted. + Want NameID Encrypted - Indicates a requirement for the incoming NameID received by this SP to be encrypted.
      • - Want Message Signed - Choose whether the SP expects incoming messages to be signed. + Want Message Signed - Choose whether the SP expects incoming messages to be signed.
      • - Digest Algorithm - Encryption algorithm to encode outgoing and decode incoming metadata. + Digest Algorithm - Encryption algorithm for the DigestValue, which is part of the validation process to ensure the integrity of the XML message.
      • - Signature Algorithm - Encryption algorithm to encode/decode signatures. + Signature Algorithm - Encryption algorithm for the message Signature.
    @@ -1075,7 +1106,7 @@

    • - SAML Logout - Choose whether user is logged out of SAML session and possibly redirect them elsewhere. + SAML Logout - Choose whether user is logged out of the SAML session using SLO.
      • If enabled, use SAML standard logout mechanism retreived from IDP metadata. @@ -1092,7 +1123,8 @@
    AUTOPROVISION
    -
    Provision PDA user privileges based on SAML Attributes. +

    + Assert user Admin status and associated Accounts with SAML Attributes.
    • Admin - Attribute to get admin status from.
      @@ -1106,13 +1138,17 @@ what's in the login assertion.
      Accounts that don't exist will be created and the user added to them.
    • +
    + Provision PDA Role/Domains/Accounts based on urn SAML 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 SAML SP db every time they log in.
      NOTE:This feature and the assertion of "Admin / Account" attributes are mutually exclusive.
      If used, the values for Admin/Account given above will be ignored.
    • - Roles provisioning field - The urn value of the attribute in the SAML Token where PDA will look for a new Role and/or new associations to domains/accounts. + Roles provisioning field - The urn value of the attribute in the SAML Token where PDA will look for a new Role and/or new associations to domains/accounts.
      + e.g. urn:oid:x.x.x.x.x
      The allowed syntax for records inside this attribute in your SAML Token is:
        if PDA-Role∈[Administrator, Operator]:
          @@ -1130,7 +1166,8 @@
      • - Urn prefix - The prefix used before the static keyword "powerdns-admin" for your entitlements in the SAML token. Must comply with RFC no.8141. + Urn prefix - The prefix used before the static keyword "powerdns-admin" for your entitlements in the SAML token. + Must comply with RFC no.8141.
        e.g.urn:mace:<your_organization>
      • Purge Roles If Empty - If toggled on, SAML logins that have no valid "powerdns-admin" records to their autoprovisioning field, @@ -1158,6 +1195,13 @@ {%- endassets %}