Table of Contents


NOTE

This is a very rought write-up on how to use Keycloak SAML2 with a Matrix Syanpse 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, using the current code of GitHub matrix-org/synapse - Branch rav/saml2_client.

In addition to being on that specific branch you need to have the pysaml2 Python module installed.

xmlsec1 must be installed on the host(s) too. On Debian and CentOS (possibly all RHEL based OSes) the package is called xmlsec1.

Be sure to verify that the path 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 server 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

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

Keycloak Client Settings

Create a new Mapper called id which will be called uid as the SAML attribute name:

Keycloak Client Mappers List

To keep everything in a “unified” form, name the mapper uid! In the screenshot it is named id, but that doesn’t matter in the end (as long as the SAML Attribute Name is correctly set to uid)

Keycloak Client Mapper id Options

Now download the Keycloak IDP 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.

Files

Replacements

Be sure to replace the following strings with your value:

Your Matrix Synapse Homeserver Config YAML file

 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"
[...]

/synapse/config/sp_conf.py

 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": ["mail", "surname", "givenname"],
            "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/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 “Keycloak IDP file” as mentioned in the above Keycloak setup steps, you can just use that one and copy it to /synapse/config/idp.xml.

Keycloak IDP 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.

Be sure to copy it to /synapse/config/idp.xml afterwards.

Example Keycloak SAML 2.0 Identity Provider Metadata:

 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/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
MAP = {
    "identifier": "urn:oasis:names:tc:SAML:2.0:attrname-format:uri",
    "fro": {
        'uid': 'uid',
    },
    "to": {
        'uid': 'uid',
    }
}

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 server logs for errors and / or warnings.

Have Fun!