Trying to troubleshoot Saml2 SSO between Zulip and Plone

I have configured Plone as a Saml2 Identity Provider (IdP) and I'm trying to use it to provide Saml based authentication with a Zulip server.

On the Plone side
So far I've

  • configured collective.saml2
  • configured an IdP at mysite.com/saml2idp
  • and auth is at mysite.com/saml2auth

I'm not sure what to put as the external attribute information under saml2idp

Also Zulip is the SP in this case and I'm not sure what endpoint on my Plone saml2 setup to "point" it at.

At this point I'm either using the wrong endpoint or my attributes are implemented incorrectly under mysite.com/saml2idp

I believe that collective.saml2 is based on dm.zope.saml2. If this is really the case, then the integration is strongly based on (SAML2) metadata. You can then obtain the SAML2 metadata describing your Plone SAML2 authority by visiting the URL url_to_SAML2_authority/metadata; you would need to obtain the Zulip metadata and use its URL in an SAML2 entity definition. If Zulip is metadata based, too, you do not need to worry about endpoints: all relevant endpoint information is in the metadata and you need to tell it only from which URL it should obtain the metadata.

For elementary SSO, you usually do not need attributes. If attributes are required, the partner entity's metadata should tell you which attributes are potentially relevant. You can then define them in your identity provider.

Thanks @dieter,
Yes it is dm.zope.saml2 based.
Here's the metadata I'm getting from my site (I've shortened the X509Certificate entry):
something weird is that the Plone instance is appended to the Service URL
I expect it to say mysite.com/saml2idp ... NOT mysite.com/Plone/saml2idp....

<ns1:EntityDescriptor xmlns:ns1="urn:oasis:names:tc:SAML:2.0:metadata" xmlns:ns2="http://www.w3.org/2000/09/xmldsig#" xmlns:ns3="urn:oasis:names:tc:SAML:2.0:assertion" ID="_4d4f2cc2-cf8f-422b-9bcd-2167375fbfc2" entityID="saml2auth" validUntil="2021-05-28T18:36:10.762752Z">
<ns1:IDPSSODescriptor protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol">
<ns1:KeyDescriptor use="signing">
<ns2:KeyInfo>
<ns2:X509Data>
<ns2:X509Certificate>MIID...nOvipQ0JDCj+NpcSy1x3w==</ns2:X509Certificate>
</ns2:X509Data>
</ns2:KeyInfo>
</ns1:KeyDescriptor>
<ns1:NameIDFormat>urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified</ns1:NameIDFormat>

<ns1:SingleSignOnService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect" Location="https://mysite.com/Plone/saml2idp/redirect"/>

<ns1:SingleSignOnService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Location="https://mysite.com/Plone/saml2idp/post"/>

<ns3:Attribute FriendlyName="login_name" Name="attr_user_permanent_id" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:unspecified"/>

</ns1:IDPSSODescriptor>

</ns1:EntityDescriptor>

I've now updated the configuration on the Zulip side.
At this point the Zulip server seems to call the endpoint as expected. Based on reading the metadata.xml I used https://mysite.com/saml2idp/redirect.

Zulip is Django based and the saml configuration is managed as a dictionary in a settngs.py file. The key configuration entry is the SOCIAL_AUTH_SAML_ENABLED_IDPS.

SOCIAL_AUTH_SAML_ENABLED_IDPS = {
    ## The fields are explained in detail here:
    ##     https://python-social-auth.readthedocs.io/en/latest/backends/saml.html
    "mysite": {
        ## Configure entity_id and url according to information provided to you by your IdP:
        "entity_id": "saml2idp",
        "url": "https://mysite.com/saml2idp/redirect",
      ##  "attr_user_permanent_id": "login_name",
      ##      "attr_first_name": "first_name",
     ##      "attr_last_name": "last_name",
     ##       "attr_username": "email",
     ##       "attr_email": "email",
   
        "display_name": "Mysite",
   
    },
}

Now I have a new issue in my zope instance.log

 Module dm.zope.saml2.idpsso.idpsso, line 81, in handle_AuthnRequest
AttributeError: _failAuthRequest

I expect that saml2idp is really below Plone. In this case, the URL should work even though it is not minimal (the URL is computed from the authority's configuration and the relative path of the IDP wrt. the authority -- apparently, virtual host information is not used optimally to minimize the URL).

That was my guess. Which is why I used "https://mysite.com/saml2idp/redirect" without the "/Plone" in it. Is it a bad idea to not use the /Plone since the generated metadata has it?

This is a spelling error in dm.zope.saml2: the name should be _failAuthnRequest not failAuthRequest. The error is triggered when the request wants an unsupported authentication context. A dm.zope.saml2 IDP supports a single authentication context which you specify in the IDP configuration.

Just getting comfortable with SAML. I think my weakness relates to not knowing what a proper authentication context declaration should look like. Which means I don't even know yet how to diagnose if I've declared the context improperly.

Is there anyway to get a more detailed debug message?

It does not matter.

You can activate logging (--> dm.zope.saml2 documentation (on PyPI)). This allows you to inspect the exchanged SAML messages. In particular, you will be able to check which authentication contexts the SP is ready to accept (element AuthnContextClassRef).

The authentication context specifies the required/guaranteed authentication strength. As the help text for the IDP configuration variable authn_context_class tells you, you would usually expect either Password (user/password authentication over HTTP) or PasswordProtectedTransport (user/password authentication over HTTPS) (as those are the usual authentication methods supported by Zope/Plone).

1 Like

Just dumping this here for reference, while I troubleshoot. This is the complete traceback I got by launching with logging enabled using:

SAML2_ENABLE_LOGGING=1;bin/instance start
to launch the instance

  <saml:Issuer>https://samlsp.mysite.com</saml:Issuer>
    <samlp:NameIDPolicy
        Format="urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified"
        AllowCreate="true" />
    <samlp:RequestedAuthnContext Comparison="exact">
        <saml:AuthnContextClassRef>urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport</saml:AuthnContextClassRef>
    </samlp:RequestedAuthnContext>
</samlp:AuthnRequest>
  Module dm.zope.saml2.idpsso.idpsso, line 81, in handle_AuthnRequest

This means that the SP requests a login over HTTPS. Check if your (Zope) application can enforce this; in this case, reconfigure your IDP to provide PasswordProtectedTransport as authentication context class. Otherwise, check whether the SP can live with Password and reconfigure it accordingly.

Thanks @dieter ...Not sure where under my saml2idp to do that, but I'm off to go figure it out.

Found it! :tada:
image

Changing it to "PasswordProtectedTransport" worked... almost. I now get a login prompt from the Zope server. But then I get an error when I try to move on.

It's a KeyError related to my SP
KeyError: u'https://zulipsp.mysite.com'

  Module ZPublisher.mapply, line 77, in mapply
  Module ZPublisher.Publish, line 48, in call_object
  Module dm.zope.saml2.idpsso.idpsso, line 188, in idpsso_logged_in
  Module dm.zope.saml2.idpsso.idpsso, line 118, in _okAuthnRequest
  Module dm.zope.saml2.idpsso.idpsso, line 144, in _make_authn_assertion
  Module dm.zope.saml2.role, line 204, in subject_from_member
  Module dm.zope.saml2.role, line 348, in get_role_descriptor
  Module dm.saml2.metadata, line 362, in metadata_by_id
  Module UserDict, line 40, in __getitem__
KeyError: u'https://zulipsp.mysite.com'

I'm off to look for what needs to be adjusted in my Plone site.

SAML2 requires that all participating (SAML) entities know one another. Corresponding entity descriptions are managed as content objects below the (SAML) authority. As I wrote earlier, dm.zope.saml2 is metadata based: the usual way to make a partner entity known is via an entity by url object: it takes the url for the entities metadata as parameter and does everything else automatically (based on the metadata description). Should your SP not provide describing metadata, the easiest thing is to describe it in a manually created metadata file and use a file url with entity by url.

yup... more progress.
I had to create an entry under my saml2auth with
id - https://zulipsp.mysite.com
url - https://zulipsp.mysite.com/saml/metadata.xml

The error is gone and now I have a failed to load key file error:

2021-05-29T15:17:10 ERROR Zope.SiteErrorLog 1622301430.80.20729305271 https://samldemo.alteroo.com/saml2idp/redirect
Traceback (innermost last):
  Module ZPublisher.Publish, line 138, in publish
  Module ZPublisher.mapply, line 77, in mapply
  Module ZPublisher.Publish, line 48, in call_object
  Module dm.zope.saml2.browser.role, line 43, in redirect
  Module dm.zope.saml2.browser.role, line 78, in _process
   - __traceback_info__: <samlp:AuthnRequest
  xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol"
  xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion"
  ID="ONELOGIN_b2d86ca659e7ffecc007c0fcfa370b72af12d6a3"
  Version="2.0"
    ProviderName="SAML Zulip"
  IssueInstant="2021-05-29T15:14:26Z"
  Destination="https://mysite.com.com/saml2idp/redirect"
  ProtocolBinding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"
  AssertionConsumerServiceURL="https://zulipsp.mysite.com/complete/saml/">
    <saml:Issuer>https://samlsp.mysite.com</saml:Issuer>
    <samlp:NameIDPolicy
        Format="urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified"
        AllowCreate="true" />
    <samlp:RequestedAuthnContext Comparison="exact">
        <saml:AuthnContextClassRef>urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport</saml:AuthnContextClassRef>
    </samlp:RequestedAuthnContext>
</samlp:AuthnRequest>
  Module dm.zope.saml2.idpsso.idpsso, line 99, in handle_AuthnRequest
  Module dm.zope.saml2.idpsso.idpsso, line 138, in _okAuthnRequest
  Module dm.zope.saml2.role, line 116, in deliver_success
  Module dm.zope.saml2.role, line 154, in deliver
  Module dm.zope.saml2.role, line 259, in http_post
  Module dm.saml2.binding.httppost, line 28, in encode
  Module pyxb.binding.basis, line 539, in toxml
  Module pyxb.binding.basis, line 520, in toDOM
  Module dm.saml2.signature, line 311, in finalize
  Module dm.saml2.signature, line 154, in sign
  Module dm.zope.saml2.authority, line 406, in sign
  Module dm.zope.saml2.authority, line 312, in _get_signature_context
  Module dm.zope.saml2.authority, line 324, in _add_sign_keys
  Module dm.xmlsec.binding._xmlsec, line 181, in dm.xmlsec.binding._xmlsec.Key.load
ValueError: ('failed to load key from file', '/home/david/demo.saml2/var/instance/../../saml.key')

I checked and the key file is located at that location. My working guess right now is that the file is incorrectly formatted.

This is how I created the key file and certificates etc..

In the root of my buildout, I generated the key and certificate:

    openssl genrsa -des3 -out saml.key 2048
    openssl req -new -key saml.key -out saml.csr
    openssl x509 -req -days 365 -in saml.csr -signkey saml.key -out saml.crt

Then I created a der file:

openssl x509 -outform der -in saml.crt -out saml.der

I also created a p12 file, which I don't use, but it's there:

    openssl pkcs12 -export -out saml.p12 -inkey saml.key -in saml.crt -certfile saml.crt

After going back to the saml2auth settings for my Plone site, I traced it to the lack of a private key password. It is now added and the sign in flow proceeds without error.
image

But it simply takes me back to my Zulip login page :man_shrugging:
Everything indicates that the Plone site is working as an identify provider now. Now I'm figuring out the "last leg" on the Zulip/SP side of things..

The SP may require more information (than the user id) in attributes. An dm.zope.saml2 IDP will deliver attributes only if they are requested via the SP's metadata description. Consult the SP documentation for integration requirements.

I commented out the attributes:

SOCIAL_AUTH_SAML_ENABLED_IDPS = {
    ## The fields are explained in detail here:
    ##     https://python-social-auth.readthedocs.io/en/latest/backends/saml.html
    "mysite": {
        ## Configure entity_id and url according to information provided to you by your IdP:
        "entity_id": "saml2idp",
        "url": "https://mysite.com/saml2idp/redirect",
      ##  "attr_user_permanent_id": "login_name",
      ##      "attr_first_name": "first_name",
     ##      "attr_last_name": "last_name",
     ##       "attr_username": "email",
     ##       "attr_email": "email",
   
        "display_name": "Mysite",
   
    },
}

I'll start by trying to see what attributes are returned by Plone/collective.saml2 and then update it in the map.