Table of Contents


NOTE

This is a very rought write-up on how to use Keycloak SAML2 with a Matrix Synapse Homeserver for user authentication. Keep in mind that not all points outlined here may 100% work for you and “turning and changing” some parameters may be needed to get it working for your setup.

Intro

This blog post is especially made for Keycloak SAML 2.0 SSO with Matrix with the GitHub matrix-org/synapse - Complete the SAML2 implementation #5422, which is based on my draft PR GitHub matrix-org/synapse - SAML2 Improvements and redirect stuff #5316.

Matrix Syanspe

To get SAML 2.0 working you need to have a Matrix Synapse Homeserver running version at least version 1.1,0.

In addition to that you need to have the pysaml2 Python module installed and xmlsec1 must be installed on the Matrix Synapse homeserver too.

On Debian and CentOS (possibly all RHEL based OSes) the package is called xmlsec1.

Be sure to verify that the path to xmlsec1 is correctly configured in the upcoming /synapse/config/sp_conf.py section. To make sure you have the right path for the pysaml2 config, run which xmlsec1 and use the printed out path for the xmlsec_binary option.

NOTE

The blog post assumes that the config for the Matrix Synapse Homeserver is located in /synapse/config/ directory, you can simply change this as long as you change it in all files and / or steps to do.

Keycloak

SAML2 Client

In Keycloak create a new SAML client and set the settings of that client as follows:

Keycloak Client Settings

Two mappers should be created:

In the end it should look like this:

Keycloak Client Mappers List

INFO

Thanks to this comment for pointing to the correct attributes to use for Matrix Synapse code to pick’em up!

SAML 2.0 Identity Provider Metadata file

Now download the Keycloak “SAML 2.0 Identity Provider Metadata” file. You can get it when you login to the “Keycloak Admin Console” and then click the “SAML 2.0 Identity Provider Metadata” link in the General tab (selected by default) at the Endpoints list.

Keycloak Realm Settings

NOTE

Should you not have this button / link in the Endpoints list, update your Keycloak instance to version 6.0.1 or higher!

If you have a very good reason to not keep your Keycloak uptodate, you can try to get the file from https://YOUR_KEYCLOAK_URL/auth/realms/YOUR_REALM/protocol/saml/descriptor. (Replace the placeholders according to your setup)

Files

Replacements

Be sure to replace the following strings with your value:

/synapse/config/key.pem and /synapse/config/cert.pem

Certificate and key from Keycloak Client “SAML Keys” tab page. If there is no certificate and key shown, press the Generate new keys button to generate them.

Click the Export button, set the following options before clicking the Download button:

You should get a file named keystore.p12 after pressing the Download button.

Now run the following sequence of commands to extract the key and cert in PEM format (this assumes the file is named keystore.p12 and the password chosen is example123):

1
2
3
4
$ export KEYSTORE_PW="example123"
$ openssl pkcs12 -in keystore.p12 -password "pass:${KEYSTORE_PW}" -nocerts -nodes | openssl rsa -out key.pem
writing RSA key
$ openssl pkcs12 -in keystore.p12 -password "pass:${KEYSTORE_PW}" -nodes | openssl x509 -out cert.pem

Two files, key.pem and cert.pem, are now generated in your current directory and now just need to be placed in the /synapse/config/ directory (full paths see section title) on the Matrix Synapse host(s).

/synapse/config/idp.xml

NOTE

If you have already downloaded the “SAML 2.0 Identity Provider Metadata” file as mentioned in the Keycloak - SAML 2.0 Identity Provider Metadata file section, you can just use and copy it to /synapse/config/idp.xml on the Matrix Synapse Homeserver.

You can get it when you login to the “Keycloak Admin Console” and then click the “SAML 2.0 Identity Provider Metadata” link in the General tab (selected by default) at the Endpoints list.

Keycloak Realm Settings

NOTE

Should you not have this button / link in the Endpoints list, update your Keycloak instance to version 6.0.1 or higher!

If you have a very good reason to not keep your Keycloak uptodate, you can try to get the file from https://YOUR_KEYCLOAK_URL/auth/realms/YOUR_REALM/protocol/saml/descriptor. (Replace the placeholders according to your setup)

Be sure to copy the downloaded file to /synapse/config/idp.xml on the Matrix Synapse Homeserver.

Example Keycloak SAML 2.0 Identity Provider Metadata file:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
<?xml version="1.0" encoding="UTF-8"?>
<!--
  ~ Copyright 2016 Red Hat, Inc. and/or its affiliates
  ~ and other contributors as indicated by the @author tags.
  ~
  ~ Licensed under the Apache License, Version 2.0 (the "License");
  ~ you may not use this file except in compliance with the License.
  ~ You may obtain a copy of the License at
  ~
  ~ http://www.apache.org/licenses/LICENSE-2.0
  ~
  ~ Unless required by applicable law or agreed to in writing, software
  ~ distributed under the License is distributed on an "AS IS" BASIS,
  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  ~ See the License for the specific language governing permissions and
  ~ limitations under the License.
  -->

<EntitiesDescriptor Name="urn:keycloak" xmlns="urn:oasis:names:tc:SAML:2.0:metadata"
					xmlns:dsig="http://www.w3.org/2000/09/xmldsig#">
	<EntityDescriptor entityID="https://__YOUR_KEYCLOAK_URL__/auth/realms/master">
		<IDPSSODescriptor WantAuthnRequestsSigned="true"
			protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol">
                        <KeyDescriptor use="signing">
                          <dsig:KeyInfo>
                            <dsig:KeyName>[REDACTED]</dsig:KeyName>
                            <dsig:X509Data>
                              <dsig:X509Certificate>[REDACTED]</dsig:X509Certificate>
                            </dsig:X509Data>
                          </dsig:KeyInfo>
                        </KeyDescriptor>

			<SingleLogoutService
					Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"
					Location="https://__YOUR_KEYCLOAK_URL__/auth/realms/master/protocol/saml" />
			<SingleLogoutService
					Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect"
					Location="https://__YOUR_KEYCLOAK_URL__/auth/realms/master/protocol/saml" />
			<NameIDFormat>urn:oasis:names:tc:SAML:2.0:nameid-format:persistent</NameIDFormat>
			<NameIDFormat>urn:oasis:names:tc:SAML:2.0:nameid-format:transient</NameIDFormat>
			<NameIDFormat>urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified</NameIDFormat>
			<NameIDFormat>urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress</NameIDFormat>
			<SingleSignOnService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"
				Location="https://__YOUR_KEYCLOAK_URL__/auth/realms/master/protocol/saml" />
			<SingleSignOnService
				Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect"
				Location="https://__YOUR_KEYCLOAK_URL__/auth/realms/master/protocol/saml" />
			<SingleSignOnService
				Binding="urn:oasis:names:tc:SAML:2.0:bindings:SOAP"
				Location="https://__YOUR_KEYCLOAK_URL__/auth/realms/master/protocol/saml" />
		</IDPSSODescriptor>
	</EntityDescriptor>
</EntitiesDescriptor>

/synapse/config/sp_conf.py

This is the pysaml2 config file. It configures pysaml2 to talk with the Keycloak server and do SAML2 authentication.

Create this file with the following content: (Don’t forget to replace the placeholders, see Replacements section)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
import saml2
from saml2.saml import NAME_FORMAT_URI

BASE = "https://matrix.example.com/"

CONFIG = {
    "entityid": "matrix-example-com",
    "description": "Matrix Server",
    "service": {
        "sp": {
            "name": "matrix-login",
            "endpoints": {
                "single_sign_on_service": [
                    (BASE + "_matrix/saml2/authn_response", saml2.BINDING_HTTP_POST),
                ],
                "assertion_consumer_service": [
                    (BASE + "_matrix/saml2/authn_response", saml2.BINDING_HTTP_POST),
                ],
                #"single_logout_service": [
                #    (BASE + "_matrix/saml2/logout", saml2.BINDING_HTTP_POST),
                #],
            },
            "required_attributes": ["uid",],
            "optional_attributes": ["displayName"],
            "sign_assertion": True,
            "sign_response": True,
        }
    },
    "debug": 0,
    "key_file": "/synapse/config/key.pem",
    "cert_file": "/synapse/config/cert.pem",
    "encryption_keypairs": [
        {
            "key_file": "/synapse/config/key.pem",
            "cert_file": "/synapse/config/cert.pem",
        }
    ],
    "attribute_map_dir": "/synapse/saml2_attribute_maps/",
    "metadata": {
        "local": ["/synapse/config/idp.xml"]
    },
    # If you want to have organization and contact_person for the pysaml2 config
    #"organization": {
    #    "name": "Example AB",
    #    "display_name": [("Example AB", "se"), ("Example Co.", "en")],
    #    "url": "http://example.com/roland",
    #},
    #"contact_person": [{
    #    "given_name": "Example",
    #    "sur_name": "Example",
    #    "email_address": ["[email protected]"],
    #    "contact_type": "technical",
    #    },
    #],
    # Make sure to have xmlsec1 installed on your host(s)!
    "xmlsec_binary": "/usr/bin/xmlsec1",
    "name_form": NAME_FORMAT_URI,
}

/synapse/saml2_attribute_maps/map.py

This file is your way to map attributes coming from the SSO (/ IDP) service.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
MAP = {
    "identifier": "urn:oasis:names:tc:SAML:2.0:attrname-format:uri",
    "fro": {
        'uid': 'uid',
        'displayName': 'displayName',
    },
    "to": {
        'uid': 'uid',
        'displayName': 'displayName',
    }
}

NOTE

fro in the above file is not a typo, see pysaml2 Documentation - “Configuration of pySAML2 entities” - attribute_map_dir.

Your Matrix Synapse Homeserver Config YAML file

Add or change the following lines to your Matrix Synapse Homeserver config (make sure you don’t duplicate the lines as that may lead to weird server behavior and / or issues):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
[...]
# Enable SAML2 for registration and login. Uses pysaml2
# config_path:      Path to the sp_conf.py configuration file
# idp_redirect_url: Identity provider URL which will redirect
#                   the user back to /login/saml2 with proper info.
# See pysaml2 docs for format of config.
saml2_config:
  enabled: true
  config_path: "/synapse/config/sp_conf.py"
  idp_redirect_url: "https://__KEYCLOAK_SERVER__/auth/realms/__KEYCLOAK_REALM__/protocol/saml"
[...]

End

That should be it, now when you go to your Riot Webapp (and chose your Matrix Homeserver) it should give you a button to login through your (SAML2) SSO.

Riot Webapp SSO Login

If it does not work, make sure your Matrix Synapse Homeserver has the required pysaml2 module installed and check your Synapse Homeserver logs for errors and / or warnings.

Have Fun!