diff --git a/powerdnsadmin/routes/admin.py b/powerdnsadmin/routes/admin.py index 86f068c..70fa563 100644 --- a/powerdnsadmin/routes/admin.py +++ b/powerdnsadmin/routes/admin.py @@ -1759,8 +1759,8 @@ def setting_authentication(): 'msg': 'Saved successfully. Please reload PDA to take effect.' } - global saml - saml = SAML() + # # 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 bc128d2..32534c8 100644 --- a/powerdnsadmin/routes/index.py +++ b/powerdnsadmin/routes/index.py @@ -917,6 +917,8 @@ def saml_login(): auth = saml.init_saml_auth(req) redirect_url = OneLogin_Saml2_Utils.get_self_url(req) + url_for( 'index.saml_authorized') + if auth is None: + return render_template('errors/SAML.html') return redirect(auth.login(return_to=redirect_url)) @@ -928,8 +930,15 @@ def saml_metadata(): req = saml.prepare_flask_request(request) auth = saml.init_saml_auth(req) + if auth is None: + return render_template('errors/SAML.html') settings = auth.get_settings() - metadata = settings.get_sp_metadata() + try: + metadata = settings.get_sp_metadata() + except: + current_app.logger.error( + "SAML: Error fetching SP Metadata") + return render_template('errors/SAML.html') errors = settings.validate_metadata(metadata) if len(errors) == 0: @@ -947,6 +956,8 @@ def saml_authorized(): abort(400) req = saml.prepare_flask_request(request) auth = saml.init_saml_auth(req) + if auth is None: + return render_template('errors/SAML.html') auth.process_response() current_app.logger.debug( auth.get_attributes() ) errors = auth.get_errors() @@ -1147,8 +1158,13 @@ def uplift_to_admin(user): @index_bp.route('/saml/sls') def saml_logout(): + if not Setting().get('saml_enabled'): + current_app.logger.error("SAML authentication is disabled.") + abort(400) req = saml.prepare_flask_request(request) auth = saml.init_saml_auth(req) + if auth is None: + return render_template('errors/SAML.html') url = auth.process_slo() errors = auth.get_errors() if len(errors) == 0: diff --git a/powerdnsadmin/services/saml.py b/powerdnsadmin/services/saml.py index 3c8e658..22d8f1d 100644 --- a/powerdnsadmin/services/saml.py +++ b/powerdnsadmin/services/saml.py @@ -25,7 +25,8 @@ 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_sso_binding=Setting().get('saml_idp_sso_binding'), + required_slo_binding=Setting().get('saml_idp_slo_binding')) except: self.idp_data = None else: @@ -36,23 +37,18 @@ class SAML(object): except: self.idp_data = None if self.idp_data is None: - current_app.logger.info( + current_app.logger.error( 'SAML: IDP Metadata initial load failed') 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? - # 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: + minutes=int(Setting().get('saml_metadata_cache_lifetime'))) + if not hasattr(self,'idp_timestamp'): self.retrieve_idp_data() - except: - return None - if self.idp_timestamp + lifetime < datetime.now(): + elif self.idp_timestamp + lifetime < datetime.now(): background_thread = Thread(target=self.retrieve_idp_data()) background_thread.start() @@ -165,9 +161,10 @@ class SAML(object): settings['sp']['singleLogoutService'][ 'binding'] = Setting().get('saml_idp_slo_binding') settings['sp']['singleLogoutService']['url'] = own_url + '/saml/sls' - settings['idp'] = metadata['idp'] + if metadata is not None and 'idp' in metadata: + settings['idp'] = metadata['idp'] settings['strict'] = True - settings['debug'] = Setting().get('saml_debug') + settings['debug'] = bool(Setting().get('saml_debug')) settings['security'] = {} settings['security'][ 'digestAlgorithm'] = Setting().get('saml_digest_algorithm') @@ -176,17 +173,17 @@ class SAML(object): settings['security']['requestedAuthnContext'] = True settings['security'][ 'signatureAlgorithm'] = Setting().get('saml_signature_algorithm') - settings['security']['wantAssertionsEncrypted'] = Setting().get('saml_want_assertions_encrypted') + settings['security']['wantAssertionsEncrypted'] = bool(Setting().get('saml_want_assertions_encrypted')) settings['security']['wantAttributeStatement'] = True settings['security']['wantNameId'] = True - 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'] = Setting().get('saml_want_nameid_encrypted') + settings['security']['authnRequestsSigned'] = bool(Setting().get('saml_sign_authn_request')) + settings['security']['logoutRequestSigned'] = bool(Setting().get('saml_sign_logout_request_response')) + settings['security']['logoutResponseSigned'] = bool(Setting().get('saml_sign_logout_request_response')) + settings['security']['nameIdEncrypted'] = bool(Setting().get('saml_nameid_encrypted')) + settings['security']['signMetadata'] = bool(Setting().get('saml_sign_metadata')) + settings['security']['wantAssertionsSigned'] = bool(Setting().get('saml_want_assertions_signed')) + settings['security']['wantMessagesSigned'] = bool(Setting().get('saml_want_message_signed')) + settings['security']['wantNameIdEncrypted'] = bool(Setting().get('saml_want_nameid_encrypted')) settings['contactPerson'] = {} settings['contactPerson']['support'] = {} settings['contactPerson']['support']['emailAddress'] = Setting().get('saml_sp_contact_mail') @@ -199,5 +196,10 @@ class SAML(object): settings['organization']['en-US']['displayname'] = 'PowerDNS-Admin' settings['organization']['en-US']['name'] = 'PowerDNS-Admin' settings['organization']['en-US']['url'] = own_url - auth = self.OneLogin_Saml2_Auth(req, settings) + try: + auth = self.OneLogin_Saml2_Auth(req, settings) + except: + current_app.logger.error( + "SAML: SAML Authentication failed") + auth = None return auth diff --git a/powerdnsadmin/templates/admin_setting_authentication.html b/powerdnsadmin/templates/admin_setting_authentication.html index 044c588..210a50b 100644 --- a/powerdnsadmin/templates/admin_setting_authentication.html +++ b/powerdnsadmin/templates/admin_setting_authentication.html @@ -810,7 +810,7 @@
- +
@@ -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.