14.05.2018 by Jonatan Zint

Setup single-sign-on (SSO) for your organization

Self-hosted SSO is not as hard and complex as you might think

Especially for smaller organizations the integration of Single-Sign-On (SSO) solutions seems like a overwhelming task. In consequence, like in many small business and startups I have seen, each application has its own database and authentication mechanism.

The advantages of having a central authentication system are obvious, since you get advantages like:

  • Easier password management for users (they only have to remember one)
  • Centralized definition of authentication policies (2FA, password requirements, e-mail validation etc.)
  • Managing on- and offboarding members and changing roles means less administration overhead if you only have to do it in one place

Nevertheless I usually hesitated to introduce SSO and Directory Services for small teams, since the prominent solutions like an OpenLDAP or Windows AD were way to heavyweight and complex to setup and the productive gain wouldn’t justify the invested time.

I am aware, that there are some SaaS (even Google does SSO via SAML2.0) providers actually making it easy for you taking away the hosting / maintenance and giving you specific integration tutorials for their service, but giving our whole authentication and rights management to a third party in the cloud wasn’t an option for us. Security concerns aside we think that decentralization of the internet is indeed desirable.

For ctrl.alt.coop I evaluated some self-hosted solutions to see if I could get this done in a reasonable amount of time, ready to be disappointed and live with the fact that maintaining authentication in each tool would be easier in the end.

So I was positively surprised that Keycloak1, an Identity and Access Management solution by redhat, was so easy to setup that I would do it again anytime in an environment with more than one application and more than three users.

In the following I will document the steps taken to install Keycloak via docker and integrate GitLab2 . Besides the official documentation of the applications, I got my knowledge from this helpful blog post .

But first let’s install Keycloak. The machine in this tutorial runs Debian 9 (stretch).

I can haz easy deployment?

Keycloak is Java software, preferably deployed with a wildfly server. When I read that I almost discarded it, since I didn’t want to bother too much about how to properly maintain a Java stack. I basically have no knowledge about doing so and my aim in this was mainly to get something up and running with minimum effort, so learning how to properly set up a reasonably big Java stack like this was out of question. Thankfully there is a well maintained docker distribution with all necessary components bundled inside. You only have to provide a Postgres or MariaDB connection, but with docker-compose you can have that almost config-free, too.

This is my docker-compose.yml:

 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
version: '2'

services:
  postgres:
      image: postgres
      volumes:
        - ./postgres:/var/lib/postgresql/data
      environment:
        POSTGRES_DB: keycloak
        POSTGRES_USER: keycloak
        POSTGRES_PASSWORD: password
  keycloak:
      image: jboss/keycloak
      environment:
        DB_VENDOR: POSTGRES
        DB_ADDR: postgres
        DB_DATABASE: keycloak
        DB_USER: keycloak
        DB_PASSWORD: password
        KEYCLOAK_USER: admin
        KEYCLOAK_PASSWORD: Pa55w0rd
        PROXY_ADDRESS_FORWARDING: "true"
        # Uncomment the line below if you want to specify JDBC parameters. The parameter below is just an example, and it shouldn't be used in production without knowledge. It is highly recommended that you read the PostgreSQL JDBC driver documentation in order to use it.
        #JDBC_PARAMS: "ssl=true"
      ports:
        - localhost:10080:8080
      depends_on:
        - postgres

Although the security concept of stuff in docker containers depends on services not being exposed to any public endpoint except explicitly specified, it never hurts to change the passwords something stronger. At least the KEYCLOAK_PASSWORD should be stronger and changed immediately after startup.

I plan to run Keycloak behind an nginx reverse proxy, therefore the PROXY_ADDRESS_FORWARDING: "true" and the mapping of port 8080 inside the container to a port of your choice in localhost is needed. I took 10080.

All application data is stored in the postgres database so mounting the container volume /var/lib/postgresql/data to some local folder can make it easy to backup all necessary data. It’s not mandatory but if you do, make sure that the folder has proper permissions - remember, all your authentication data will be in there.

Systemd and docker-compose

Of course you can just start this with docker-compose up in foreground, or with docker-compose start detached from your ssh session, but I want to have my docker-compose stacks integrated with systemd, so there is only one place where I configure all services on this system.

Take a look at this unit file I placed under /etc/systemd/system/docker-compose@.service.

 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
[Unit]
Description=%i service with docker compose
Requires=docker.service
After=docker.service

[Service]
Restart=always

WorkingDirectory=/opt/docker/compose/%i

# Remove old containers, images and volumes
ExecStartPre=/usr/bin/docker-compose down -v
ExecStartPre=/usr/bin/docker-compose rm -fv
ExecStartPre=-/bin/bash -c 'docker volume ls -qf "name=%i_" | xargs docker volume rm'
ExecStartPre=-/bin/bash -c 'docker network ls -qf "name=%i_" | xargs docker network rm'
ExecStartPre=-/bin/bash -c 'docker ps -aqf "name=%i_*" | xargs docker rm'

# Compose up
ExecStart=/usr/bin/docker-compose up

# Compose down, remove containers and volumes
ExecStop=/usr/bin/docker-compose down -v

[Install]
WantedBy=multi-user.target

This unit file allows you to control any docker-compose.yml file placed in /opt/docker/compose/<service name>. In this case we have it placed in /opt/docker/compose/keycloak and these regular systemd commands get it up and running.

1
2
$ systemctl start docker-compose@keycloak
$ systemctl enable docker-compose@keycloak # doing that will make systemd start it on boot

Reverse proxy

Last but not least we need to expose Keycloak to the public, where I chose to use a nginx reverse proxy, since I like to have my webserver configuration and ssl termination in one place. So this is a pretty standard nginx reverse proxy config:

 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
server {
    server_name auth.domain.com;
    port_in_redirect off;
    proxy_buffering off;
    proxy_http_version 1.1;     # Properly proxy websocket connections
    proxy_read_timeout 300s;    # terminate websockets afer 5min of inactivity
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;

    location / {
            proxy_pass  http://localhost:10080;
   }
    
    location ~ /\.well-known/acme-challenge {
	    root /usr/share/nginx/html/;
    }

    listen [::]:443 ssl; # managed by Certbot
    listen 443 ssl; # managed by Certbot
    ssl_certificate /etc/letsencrypt/live/auth.d/fullchain.pem; # managed by Certbot
    ssl_certificate_key /etc/letsencrypt/live/auth.domain.com/privkey.pem; # managed by Certbot
    include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
    ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot

}
server {
    if ($host = auth.domain.com) {
        return 301 https://$host$request_uri;
    } # managed by Certbot

    listen 80;
    listen [::]:80;
    server_name auth.domain.com;
    return 404; # managed by Certbot

}

Note that in in this example I used the certbot3 from letsencrypt4 to obtain SSL Certificates. Also every http request is redirected to the https endpoint, which is desirable in any case, but absolutely necessary for authentication services like this one. In this case the proxy traffic doesn’t leave the host, so having the proxy_pass communicate via http shouldn’t be an issue. If you plan to do that over insecure networks (most are!), you should make sure that you use https for that too. You now should be able to login under https://auth.domain.com with the admin default credentials from your docker-compose.yml. You now should change the password. Important: Do not delete your admin account even though you plan to use another account for administration - the docker script would recreate admin with the default password!

Keycloak login screen

Keycloak login screen

Integrate GitLab

On to GitLab. I assume you have a GitLab installed with their “Omnibus” distribution. I only going to cover the Keycloak side of the confguration since GitLab is covering SAML pretty well in their docs .

Keycloak client menu entry

Keycloak menu

First of all you need to create a client, so click on the Clients menu entry in the sidebar on the left. You should see a list of some default clients (The admin console itself for example is also just a client) and in the upper right corner of the table a create button. Click that. Some SAML2.0 client applications provide an option to generate an XML file to ease provisioning, therefore you see a field where you can upload this file. Unfortunately GitLab doesn’t. But we do! So download this file adjust it to your domain and import it. Afterwards do not forget to generate new keys in the SAML Keys tab. Then proceed here . Or fill out the form like shown in the images below (adjusted with your domain of course).

Keycloak client menu entry

Create a client

GitLab client config page 1

GitLab client config page 1

GitLab client config page 2

GitLab client config page 2

When authenticating with a third party, some fields have to be matched between application and identity provider. Therefore we need to set up some mapping in the “Mapping”-Tab. See images below.

mapper tab

mapper tab

first name mapper

first name mapper

last name mapper

last name mapper

email mapper

email mapper

user name mapper

user name mapper

roles mapper

roles mapper

certificate

Finally, to ensure authenticity of the Identity provider GitLab needs to know about keycloak’s cert. You find it in Realm Settings -> Keys. You should find a row with Type RSA. Click on “Certificate” in the most right column and you get the public key which you will need for the SAML config in /etc/gitlab/gitlab.rb which should now look similar to this:

gitlab_rails['omniauth_enabled'] = true
gitlab_rails['omniauth_allow_single_sign_on'] = ['saml']
gitlab_rails['omniauth_providers'] = [
  {
          name: 'saml',
          args: {
                  assertion_consumer_service_url: 'https://gitlab.domain.com/users/auth/saml/callback',
                  idp_cert: '[very long certificate string]'
                  idp_sso_target_url: 'https://auth.domain.com/auth/realms/master/protocol/saml/clients/gitlab.domain.com',
                  issuer: 'gitlab.domain.com',
                  attribute_statements: {
                        first_name: ['first_name'],
                        last_name: ['last_name'],
                        name: ['name'],
                        username: ['name'],
                        email: ['email']
                  },
                  name_identifier_format: 'urn:oasis:names:tc:SAML:2.0:nameid-format:persistent'
          },
          label: 'auth.domain.com auth',
          groups_attribute: 'roles',
          external_groups: ['gitlab.domain.com:external'],
  }
]

On your GitLab login screen you should now see a new option to authenticate with your new Keycloak service. If so: Congratulations!

At ctrl.alt.coop we now have several services hooked up to Keycloak making authentication way more joyful. For instance we authenticate Nextcloud, Redmine and even SSH via federated LDAP. So far I definitely would endorse Keycloak as a simple SSO Solution.


  1. https://www.keycloak.org ↩︎

  2. https://gitlab.com/ ↩︎

  3. https://certbot.eff.org/all-instructions/ ↩︎

  4. https://letsencrypt.org/ ↩︎