This is a wrapper around SimpleSAMLphp which provides a test SAML Identity Provider to use in Totara instances. It is for testing the SAML2 connections only and should not be used in any production site.
We currently embed SimpleSAMLphp version 2.0.3. If you'd like to test with the 1.19.7 line please checkout the latest v1.x tags.
| Variable | Description |
|---|---|
LISTEN_PORT |
The port used to access the service. Defaults to 8089. |
SITE_TITLE |
Override the default site title, used when running multiple to tell them apart. |
CERTIFICATES |
Which signing keys the IdP uses: default, rollover, new or expired. See Certificates. |
ENCRYPTION |
Whether the IdP encrypts assertions: off (default) or on. See Encryption. |
There is no published image - build it yourself from this repository.
# Build the image (the default target is the production stage)
git clone git@github.com:totara/simple-saml-test.git
cd simple-saml-test
docker build -t simple-saml-test:local .
# Start the service
docker run --rm -p 8089:8089 -e LISTEN_PORT=8089 \
-v simple-saml-certs:/var/www/html/cert \
-v simple-saml-sp-list:/var/www/metadata_storage \
-it simple-saml-test:localThe two named volumes keep the IdP certificates and the list of service providers across restarts and rebuilds. Without them every new container starts with the certificates baked into the image, which change on every build, and an empty service provider list. Any service provider holding the old IdP metadata then has to refresh it.
Rebuild whenever you pull this repository. To start again from scratch, remove the volumes with
docker volume rm simple-saml-certs simple-saml-sp-list.
Once started, you can access the service via http://localhost:{LISTEN_PORT} (defaults to 8089).
Open the site. The front page shows what the IdP will send (see Settings Page) and links to admin. Log in as admin, and then navigate to Manage Service Providers. Add any SP instances on the page there, the URL must be the full URL to your metadata (it does not fully validate).
Eg: http://{YOUR_SP_INSTANCE}/path/to/metadata.php
We currently do not support raw XML dumps, the SAML image must be able to download the metadata file from your Service Provider directly. You can teach docker the IP address of your service if it isn't resolvable.
# Instance is running directly on the host machine
docker run --add-host={YOUR_SP_INSTANCE}:host-gateway ... -it simple-saml-test:local
# Instance is somewhere else, replace the domain & IP
docker run --add-host={YOUR_SP_INSTANCE}:{IP_OF_SITE} ... -it simple-saml-test:localIf you're using Totara Docker Dev library, you can add this image to the service directly. Build it first, as above - the tag below refers to your local build.
Create a new file called saml.yml and add it to the custom directory in your Totara docker project.
Add the following contents:
version: "3.7"
services:
saml2:
image: simple-saml-test:local
networks:
- totara
ports:
- "8089:8089"
environment:
- LISTEN_PORT=8089
- SITE_TITLE="Testing"
- CERTIFICATES=default
volumes:
- saml2-certs:/var/www/html/cert
- saml2-sp-list:/var/www/metadata_storage
volumes:
saml2-certs:
saml2-sp-list:Make sure you add saml2 to your local hosts file, so it resolves in your browser.
Start the docker service using t up saml2.
Try and access http://saml2:8089 and confirm you see the test environment.
Important: The URL that Totara and the URL that you access the site on via your browser must be the same.
The path to the metadata file depends on what SAML plugin you are using which is why it's not specified here.
By default, there's a hard-coded list of users and attributes. However, you can provide your own PHP file via volumes and replace the user list with your own.
Create a new file called custom-auth-sources.php with the following structure:
<?php
return [
// username:password => [array of attributes]
'my_user:password1' => [
'uid' => ['my_uid'],
'username' => ['my_username'],
],
'another:password' => [
'uid' => ['another'],
'username' => ['annie_example'],
'firstname' => ['annie']
],
]The username:password section applies to the IdP, while the internal array is what will be posted back to the SP.
In the example above, the my_user user is known as my_username or my_uid to the service provider and will never see my_user.
Once created, include it as a volume, such as:
docker run ... -v /path/to/custom-auth-sources.php:/var/www/custom-auth-sources.php ... -it simple-saml-test:local
To change the settings in ./metadata/saml20-idp-hosted.php you can create a file called custom-saml20-idp-hosted.php.
Return an array of settings to override or merge into the saml20-idp-hosted.php main file.
Once created, include it as a volume, such as:
docker run ... -v /path/to/custom-saml20-idp-hosted.php:/var/www/custom-saml20-idp-hosted.php ... -it simple-saml-test:local
The image ships three key pairs in /var/www/html/cert:
server.crt/server.pem- the normal key, valid for 10 years.new_server.crt/new_server.pem- a second key, valid for 10 years.expired_server.crt/expired_server.pem- valid for one day from when it was generated, so it reads as expired after that.
Any that are missing are generated when the container starts, so an empty volume or bind mount over the directory is filled in on first start and then keeps those keys.
Pick which keys the IdP uses with the CERTIFICATES environment variable:
| Value | Metadata lists | Signs with |
|---|---|---|
default |
server.crt |
server.crt |
rollover |
new_server.crt first, then server.crt |
server.crt |
new |
new_server.crt |
new_server.crt |
expired |
expired_server.crt |
expired_server.crt |
Any other value fails every request with Unknown CERTIFICATES value, so a typo cannot pass for a working test.
rollover is the state an IdP is in part way through a key rollover. The certificate it signs with is listed second,
so it catches a service provider that only checks the first signing certificate in the metadata.
docker run ... -e CERTIFICATES=rollover ... -it simple-saml-test:localWith docker-compose, CERTIFICATES=rollover docker-compose up dev works without editing the file.
After changing CERTIFICATES, refresh the IdP metadata in the service provider.
Keys set in custom-saml20-idp-hosted.php override these one by one, for example to point at a certificate of your own
in a mounted cert directory.
Assertions are sent unencrypted by default. Turn encryption on with the ENCRYPTION environment variable:
docker run ... -e ENCRYPTION=on ... -it simple-saml-test:localWith docker-compose, ENCRYPTION=on docker-compose up dev works without editing the file.
Any value other than on or off fails every request with Unknown ENCRYPTION value, like CERTIFICATES.
With encryption on, the service provider's metadata must include an encryption certificate, otherwise every login
fails with Missing encryption key for entity. A service provider's own metadata can still turn encryption on or off
for that provider, and 'assertion.encryption' in custom-saml20-idp-hosted.php wins over ENCRYPTION.
When an assertion is encrypted, the IdP generates a one-off session key to encrypt it with, then wraps that session key with the SP's public key. The algorithm used for the wrapping is the key transport algorithm, and it is declared separately from the block cipher that encrypts the assertion body.
SimpleSAMLphp only ever uses rsa-oaep-mgf1p, the XML Encryption 1.0 algorithm, which fixes both the OAEP digest and
the MGF1 hash to SHA-1. Hardened identity providers instead use the XML Encryption 1.1 rsa-oaep, which declares its
digest and MGF1 hash explicitly and is normally paired with SHA-256. An SP that assumes SHA-1 cannot decrypt those
assertions at all, so this image can be told to emit them.
Add a key_transport key to your custom-saml20-idp-hosted.php:
return [
'key_transport' => [
'mode' => 'rsa-oaep', // 'stock' (default) leaves SimpleSAMLphp alone, 'rsa-1_5' see below
'block' => 'aes256-gcm', // aes128-cbc | aes256-cbc | aes128-gcm | aes256-gcm
'digest' => 'sha256', // OAEP digest: sha1 | sha256 | sha384 | sha512
'mgf' => 'sha256', // MGF1 hash: sha1 | sha256 | sha384 | sha512
],
];SimpleSAMLphp ignores the unknown key, so it can sit alongside the rest of your IdP overrides. If you would rather keep
it separate, mount the same array on its own at /var/www/key-transport.php.
The default is stock, so without this the image behaves exactly as it always has.
With mode => 'rsa-oaep' the <xenc:EncryptedKey> is emitted as:
<xenc:EncryptionMethod Algorithm="http://www.w3.org/2009/xmlenc11#rsa-oaep">
<ds:DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
<xenc11:MGF Algorithm="http://www.w3.org/2009/xmlenc11#mgf1sha256"/>
</xenc:EncryptionMethod>Useful combinations when testing an SP:
| Setting | What it tells you |
|---|---|
mode => 'stock' |
The unchanged path. Always worth re-running as a regression check. |
mode => 'rsa-oaep', sha256/sha256 |
The configuration hardened identity providers use. |
mode => 'rsa-oaep', sha1/sha1 |
Control: isolates the digest from the algorithm URI, since only the URI changes from stock. |
mode => 'rsa-oaep', sha256 digest, sha1 mgf |
The two hashes are declared separately and need not match. |
With mode => 'rsa-1_5' the session key is wrapped with RSA PKCS#1 v1.5 instead, and declared with no parameters:
<xenc:EncryptionMethod Algorithm="http://www.w3.org/2001/04/xmlenc#rsa-1_5"/>digest and mgf are ignored in that mode, block still applies. rsa-1_5 is deprecated, so this is for checking
what an SP does with it, not for a normal setup.
An SP that cannot handle a given combination usually reports only a generic decryption failure, because the algorithms are negotiated in the ciphertext rather than the handshake. Expect to read the SP's own logs rather than anything the IdP reports, and be wary of matching on a specific OpenSSL error code - OpenSSL's error queue is global, so a stale entry is often what surfaces.
The key transport can also be picked in the browser, without editing the mounted file. Both places below offer the same fixed presets:
| Preset | Sends |
|---|---|
configured |
Whatever the mounted file or the default says. This is the default choice. |
stock |
rsa-oaep-mgf1p, SHA-1 |
rsa-oaep-sha256-sha256 |
rsa-oaep, digest SHA-256, MGF1 SHA-256 |
rsa-oaep-sha256-sha1 |
rsa-oaep, digest SHA-256, MGF1 SHA-1 |
rsa-oaep-sha1-sha1 |
rsa-oaep, digest SHA-1, MGF1 SHA-1 |
rsa-oaep-sha512-sha512 |
rsa-oaep, digest SHA-512, MGF1 SHA-512 |
rsa-1_5 |
rsa-1_5 |
The block cipher is not part of a preset, it comes from the mounted file or the default (aes256-gcm).
- Login page: the IdP login form has a "Key transport" dropdown when the login is for a service provider. The admin login does not show it, since the choice only applies to assertions sent to a service provider.
- Settings page: the front page has the same dropdown. See below.
The choice is kept in the IdP session and wins over the mounted file until the session ends. Once you are logged in at the IdP, later logins from a service provider skip the login page, so the dropdown there is not shown again. Change it on the settings page, or log out of the IdP to see the login page again.
Assertion encryption must still be on (see Encryption), otherwise nothing is encrypted and the choice has no effect.
The front page (/module.php/totara/) shows what the IdP will send right now:
- whether assertion encryption is on, with a warning when it is off
- the key transport in effect (mode, block cipher, OAEP digest, MGF1 hash) and where it came from:
session,fileordefault - the
ENCRYPTIONandCERTIFICATESvalues, the entity ID and the number of service providers - whether you are logged in at the IdP
It also has the key transport dropdown, and links to admin, Manage Service Providers and IdP logout. It needs no login.
- Fork this repo, create a new branch and make the change.
- Test using the built-in docker image with docker-compose, you can run
docker-compose up --build devto run the dev version with the config/metadata/modules folders volumed in ( real time changes). - Once everything is all good, test with the prod version
docker-compose up --build prod. - If it is all good, submit a pull request for the change.
- Fork this repo, create a new branch
- Edit the Dockerfile and change the
SAML_VERSIONbuild argument to the new version you want to include - Check any upgrade notes about things that must change, specifically look for changes that impact modules, hooks or the idp-hosted or idp-remote files.
- Refresh
modules/totara/themes/simple/core/loginuserpass.twigfrom the newmodules/core/templates/loginuserpass.twig. It is a copy with the key transport dropdown added, so it does not pick up core changes by itself. - Refresh
patches/key-transport.patchif the build fails applying it. It is applied with--fuzz=0on purpose, so a moved context line breaks the build instead of silently dropping the key transport hook. - Test using the built-in docker image with docker-compose, you can run
docker-compose up --build devto run the dev version with the config/metadata/modules folders volumed in ( real time changes). - Once everything is all good, test with the prod version
docker-compose up --build prod. - If it is all good, submit a pull request for the change.