Pre-requisites
If you haven't configured your Keycloak instance, check our other articles:
- Set up Keycloak to manage your users and their access to tools and platforms
- Use Keycloak as both Identity Broker using Keycloak as Identity Provider - Based on Keycloak Quay.io v22.0.5 (check the Identity Broker Keycloak instance setup)
In order to set up Keycloak as an Identity Broker for SSO using the Redmine OpenID plugin, you will need:
- SSH access to the plugin folder for your Redmine instance
- Be able to either upload files or pull files from a Git repository to that plugin directory
- Have Administrator level access to your Redmine instance
- Have Administrator level access to your Keycloak instance
Useful ressources:
- Keycloak Docker repository: Keycloak Docker Official Image
- Redmine Docker repository: Redmine Docker Official Image
- Redmine plugin repository: alphanodes/redmine_saml
- My own fork for the Redmine SAML plugin: nicolasmetters/redmine_saml
The setup will be done using Keycloak v22 and Redmine v5.0.5 using official and un-modified Docker images but the plugin configuration can also be done on non-container based instances.
Set up the plugin on the Redmine host server - Part 1: Get the plugin
Connect through your SSH client to your Redmine host to install the plugin.
In November 2023, I've forked version 1.0.6 of the official plugin to enable Redmine Administrator creation and add a template to the plugin's login button.
Please check the status of official Redmine SAML plugin before going further.
Install from the forked repository
cd /YOUR_REDMINE_PLUGIN_DIR/
sudo git clone https://github.com/nicolasmetters/redmine_saml.git
Select SAML SSO button visual
Wanting to keep a visual harmony to the login page, I updated the visual:
To switch visuals, simply rename the .CSS file: the plugin will call content from the file named login.css in the directory /assets/stylesheets/.
Install from the official repository
cd /YOUR_REDMINE_PLUGIN_DIR/
sudo git clone https://github.com/alphanodes/redmine_saml.git
My Redmine setup only requires that I restart the container (check the Redmine setup article to know more).
If you are not using a container-based Redmine instance, install the plugin the usual way.
cd /YOUR_REDMINE_DIR
bundle install
bundle exec rake redmine:plugins:migrate RAILS_ENV=production
rake redmine:plugins RAILS_ENV=production
sudo touch /opt/redmine/tmp/restart.txt
Configure Keycloak
For this example we have set up a Keycloak instance :
Parameter | Value |
---|---|
Redmine base URL | https://redmine.nicksopenworld.com |
Keycloak base URL | https://broker.nicksopenworld.com |
REALM | nicksopenworld |
Client ID | Redmine SAML Metadata URL value |
Get signing certificate information
In the SAML configuration you will be asked to provide either the x509 full certificate detail or just it's SHA-1 formated fingerprint.
The certificate can be found in the REALM details:
Generate x509 certificate fingerprint using Ubuntu & OpenSSL
Once you have the x509 certificate value, you can either transform it yourself on your Linux server using OpenSSL:
- Create a .crt file:
sudo vi x509.crt
- Type in the content:
-----BEGIN CERTIFICATE-----
FormattedX509CertificateValue
-----END CERTIFICATE-----
FormattedX509CertificateValue is the long string value between the "\ds:X509Certificate>" tags. - Run the command:
openssl x509 -sha1 -fingerprint -in path/to/x509.crt -noout
The fingerprint will be provided in the terminal.
Generate x509 certificate fingerprint online ressources
An easier solution is using online websites such as:
https://www.samltool.com/format_x509cert.phpCopy the long string value between the "\ds:X509Certificate>" tags from the SAML descriptor into the input box at the top:
Copy the X.509 cert with header value provided.
And then get the fingerprint itself:
https://www.samltool.com/format_x509cert.phpAnd save the Formatted FingerPrint value provided.
Signing certificate fingerprint must be provided in SHA-1.
This is due to the fact that the SAML plugin relies on omniauth-saml which itself only support SHA-1 as of November 2023.
Fun fact: this impacts other solutions also using omniauth-saml such as Gitlab.Creating a dedicated SAML client
Create a new SAML client with the following parameters:
Field | Value |
---|---|
Client type | |
Client ID | Mandatory. Structure: https://REDMINE_URL/auth/saml/metadata For our example: https://redmine.nicksopenworld.com/auth/saml/metadata |
Name | redmine |
Description | Whatever text you want |
Always display in UI | For our example: Off |
Click Next and finish configuration:
Field | Value |
---|---|
Root URL | Used to build URLs on the fly. For our example: empty |
Home URL | The Redmine instance URL. For our example: https://redmine.nicksopenworld.com |
Valid redirect URIs | Authorized redirect URL. Wildcard using URL can be used. Structure: https://REDMINE_URL/auth/saml/callback For our example: https://redmine.nicksopenworld.com/auth/saml/callback |
Valid post logout redirect URIs | Authorized URLs to manage logout. For our example: empty |
IDP-Initiated SSO URL name | To be used if supported by the SAML client. For our example: empty |
IDP Initiated SSO Relay State | To be used if supported by the SAML client. For our example: empty |
Master SAML Processing URL | Main SAML management URL. Structure: https://KEYCLOAK_URL/auth/realms/REALM_NAME/protocol/saml For our example: https://broker.nicksopenworld.com/auth/realms/nicksopenworld/protocol/saml |
SAML capabilities
Here we have to set a bit more configuration:
Field | Value |
---|---|
Name ID format | presistent |
Force name ID format | On |
Force POST binding | On |
Force artifact binding | Off |
Include AuthnStatement | On |
Include OneTimeUse Condition | Off |
Optimize REDIRECT signing key lookup | Off |
Allow ECP flow | Off |
Certificate encryption configuration
Once the client is created, make sure Signature and Encryption is set to use SHA-1:
Field | Value |
---|---|
Sign documents | On |
Sign assertions | On |
Signature algorithm | Mandatory. RSA-SHA1 |
SAML signature key name | Optional. NONE |
Canonicalization method | EXCLUSIVE |
Login/logout client configuration
You also have Login and Logout parameters. We will leave default values for our current setup.
Client scope: decide the attributes pushed by the client
Make sure you have the following attributes configured:
Name | Mapper type | User Attribute | Friendly name | SAML attribute name | SAML attribute name format | Aggergate |
---|---|---|---|---|---|---|
last_name | User Attribute | lastName | Family name | lastname | Basic | Off |
name | User Attribute | username | Full name | username | Basic | Off |
first_name | User Attribute | firstName | Given name | firstname | Basic | Off |
User Attribute | Email address | Basic | Off | |||
admin | User Attribute | admin | admin | admin | Basic | Off |
If an attribute is missing, you can create it:
Configure the "Redmine Administrator" access through SAML SSO
WARNING:
As of Novembre 2023, this configuration requires using the plugin code from nicolasmetters/redmine_saml
A pull request has been done to the official repository but is pending review.
By default, the Redmine SAML plugin will grand access to users as standard users in Redmine, even if they have been set as Administrator in Redmine's Users.
In order to be able to create Administrators, a dedicated admin attribute must be set up in the client:
Then, you must configure the attribute at the user level:
The plugin code is set to log users:
- as Administrator if attribute is present and set to true
- as Standard user if attribute is missing
- as Standard user if attribute ispresent and set to false
Switching from Standard user to Administrator only requires updating the attribute.
This can be done either directly on the user entry or by creating a dedicated group to which you assign the attribute:
And then you only have to assign the user to that group:
Set up the plugin on the Redmine host server - Part 2: Create your SAML initializer
Once the client is set up, we should have all the information needed to finish the configuration on the Redmine host server.
The plugin requires an initializer .rb file: it can have any name but must be in Redmine's config/initializers/ directory.An example can be found in the Git repository in redmine_saml/contrib/.
For example in my docker-compose.yml is declared:
volumes:
- ../config/initializers/saml.rb:/usr/src/redmine/config/initializers/saml.rbThe content for our example:
# frozen_string_literal: true require Rails.root.join('plugins/redmine_saml/lib/redmine_saml') require Rails.root.join('plugins/redmine_saml/lib/redmine_saml/base')
RedmineSaml::Base.configure do |config| config.saml = { # Redmine callback URL assertion_consumer_service_url: "https://redmine.nicksopenworld.com#{RedmineSaml::CALLBACK_PATH}",
# The issuer name / entity ID. Must be an URI as per SAML 2.0 spec. sp_entity_id: "https://redmine.nicksopenworld.com#{RedmineSaml::METADATA_PATH}",
# The SLS (logout) callback URL single_logout_service_url: "https://redmine.nicksopenworld.com#{RedmineSaml::LOGOUT_SERVICE_PATH}",
# SSO login endpoint idp_sso_service_url: 'https://broker.nicksopenworld.com/realms/nicksopenworld/protocol/saml/clients/redmine',
# SSO SSL certificate SHA-1 fingerprint idp_cert_fingerprint: '14:20:C3:EA:D9:51:31:4B:EB:42:99:EC:5A:40:A3:1D:EE:FC:93:9B',
name_identifier_format: 'urn:oasis:names:tc:SAML:2.0:nameid-format:persistent',
# Optional signout URL, not supported by all identity providers signout_url: 'https://broker.nicksopenworld.com/realms/nicksopenworld/protocol/saml/clients/redmine',
idp_slo_service_url: 'https://broker.nicksopenworld.com/realms/nicksopenworld/protocol/saml/clients/redmine',
# Which redmine field is used as name_identifier_value for SAML logout name_identifier_value: 'mail',
# overwrite mapping seperator, if required # attribute_mapping_sep: '|',
attribute_mapping: { # How will we map attributes from SSO to redmine attributes using urn:oid:identifier or friendly names # mail: 'extra|raw_info|urn:oid:0.9.2342.19200300.100.1.3' # or # mail: 'extra|raw_info|email' login: 'extra|raw_info|username', mail: 'extra|raw_info|email', firstname: 'extra|raw_info|firstname', lastname: 'extra|raw_info|lastname', admin: 'extra|raw_info|admin' } } config.on_login do |omniauth_hash, user| # Implement any hook you want here end endOnce the .rb file is set up, the Redmine SAML plugin should be accessible and usable.
Configure the Redmine SAML plugin
Plugin general configuration
Log into your Redmine using Administrator access and make sure the plugin is visible in the /admin/plugins page:
Go the the Redmine SAML plugin configuration page /settings/plugin/redmine_saml?tab=settings.
Field | Value |
---|---|
Enable SAML authentication | Disable it if you want to block SAML SSO without having to uninstall the plugin. For our example: On |
Login page text | Whatever text you want. For our example: SAML SSO |
Replace Redmine login page | If enabled, the username & password native login method for Redmine is disabled. For our example: Off |
Create users automatically? | If disabled, users need to be created in Keycloak and in Redmine separately. Disabling it greatly reduces the gain of setting up SSO but must be considered based on your security setup and limitations. For our example: SAML SSO |
Redmine & SAML general configuration
You can also check the configuration and informatin for the plugin on the Information tab:
On this page you can see the values that have been written to the .rb initializer file but also a few verifications:
This is not necessarily an error on the Redmine host server: in Redmine's Settings ( page "/settings" ), the Host name and path had been saved with an extra "/" symbol inducing a failure of the URL format test.
Once the Settings have been updated accordingly, we can confirm all is OK:
Test the SSO connection & debug
Once all is set, create a new user in your Keycloak platform that doesn't exist in Redmine and set up a password for it.
In a private navigation browser session, go to your Redmine login page and click on the SSO button. You should be redirected to the Keycloak login page.
Connect with the username and password you have just set and you should be redirected to your Redmine as a logged in user:
If the connection fails, check your Redmine logs: they should contain the SAML assertion received but can also provide error details.
For example:
[2023-11-20T20:12:33.142974 #1] DEBUG -- omniauth: (saml) Callback phase initiated.
[2023-11-20T20:12:33.165051 #1] ERROR -- omniauth: (saml) Authentication failure! invalid_ticket: OneLogin::RubySaml::ValidationError, Fingerprint mismatch
[2023-11-20T20:12:33.335371 #1] INFO -- : Started GET "/auth/failure?message=invalid_ticket&origin=https%3A%2F%2Fredmine.nicksopenworld.com%2F&strategy=saml"
[2023-11-20T20:12:33.337142 #1] INFO -- : Processing by AccountController#login_with_saml_failure as HTML
[2023-11-20T20:12:33.337301 #1] INFO -- : Parameters: {"message"=>"invalid_ticket", "origin"=>"https://redmine.nicksopenworld.com/", "strategy"=>"saml"}
[2023-11-20T20:12:33.367781 #1] INFO -- : Current user: anonymous
[2023-11-20T20:12:33.368497 #1] WARN -- : login_with_saml_failure: error_saml_invalid_ticket
[2023-11-20T20:12:33.369124 #1] INFO -- : Redirected to https://redmine.nicksopenworld.com/login
[2023-11-20T20:12:33.369426 #1] INFO -- : Completed 302 Found in 32ms (ActiveRecord: 1.6ms | Allocations: 1110)
Here we can see invalid_ticket but also the reason of it: OneLogin::RubySaml::ValidationError, Fingerprint mismatch
In this case, the reason was that I was checking SHA-256 support (which it is not) so the system was not capable of decoding the fingerprint provided.
But I've also have the error when putting the wrong fingerprint string value in my saml.rb initializer configuration file.
Conclusion
With this, you should now have a simplified way to manage your Redmine Users.
Please note that this plugin can be used on a Redmine instance on which are also installed:
- Redmine Openid Connect plugin: meaning you could choose OpenID for Keycloak/Redmine SSO configuration
- Redmine Omniauth gitlab plugin: which is used to allow users created and managed in Gitlab to connect to Redmine. Check the Gitlab/Redmine SSO configuration for more details.