Categories
Apache Encryption IdP Keycloak Linux SAML Shibboleth SingleSignOn SSO Webserver

Apache SAML authentication with mod_shib

Till now I was mainly using mod_auth_mellon in order to do SAML-based authentication for web services. I did so because it seemed quite easy to configure (and the Ubuntu mod_shib package had a nasty bug some years ago when I made first contact with SAML). However mod_shib (part of the shibboleth service provider (SP) implenetation) is more or less the standard for that usage scenario so I finally decided to give it one more try.

In contrast to mod_auth_mellon that implements all the SP functionality within the apache module, mod_shib uses an external daemon (shibd) to do most of the work. Consequently most of the configuration is not done in apache but for this daemon.

In case you’ve read some of my other blog articles I don’t need to tell you: my test system is Ubuntu 24.04 (LTS) – so all following instruction were tested on that kind of system.

Installation and configuration

Assuming you already have an apache server up and running, we only need to install two additional packages: the shibboleth utils (containing the shibd daemon) and the apache authentication module:

linux # apt install libapache2-mod-shib shibboleth-sp-utils

Shibboleth configuration

Fortunately these packages come with a basic pre-configuration and a set of scripts to ease the configuration. The two we’ll use are:

shib-keygen
shib-metagen

First of all we’ll use shib-keygen to create a key pair for our service provider (SP) that’ll be used to encrypt and/or sign our SAML messages.

Generate key pair using the system’s hostname and the default certificate/key file names:

linux # shib-keygen
# Will generate (using set hostname "sp.mydomain.de"):
#  /etc/shibboleth/sp-key.pem
#  /etc/shibboleth/sp-cert.pem

After that we need to generate metadata for our SP. For now we’ll also use the default settings:

linux # shib-metagen -h sp.mydomain.de -c sp-cert.pem -e https://sp.mydomain.de
<md:EntityDescriptor xmlns:md="urn:oasis:names:tc:SAML:2.0:metadata" xmlns:ds="http://www.w3.org/2000/09/xmldsig#" entityID="https://sp.mydomain.de">
  <md:SPSSODescriptor protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol urn:oasis:names:tc:SAML:1.1:protocol">
    <md:KeyDescriptor>
      <ds:KeyInfo>
        <ds:X509Data>
          <ds:X509Certificate>
            <... certificate data here ...>
          </ds:X509Certificate>
        </ds:X509Data>
      </ds:KeyInfo>
    </md:KeyDescriptor>
    <md:AssertionConsumerService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Location="https://sp.mydomain.de/Shibboleth.sso/SAML2/POST" index="1"/>
    <md:AssertionConsumerService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST-SimpleSign" Location="https://sp.mydomain.de/Shibboleth.sso/SAML2/POST-SimpleSign" index="2"/>
    <md:AssertionConsumerService Binding="urn:oasis:names:tc:SAML:2.0:bindings:PAOS" Location="https://sp.mydomain.de/Shibboleth.sso/SAML2/ECP" index="3"/>
    <md:AssertionConsumerService Binding="urn:oasis:names:tc:SAML:1.0:profiles:browser-post" Location="https://sp.mydomain.de/Shibboleth.sso/SAML/POST" index="4"/>
  </md:SPSSODescriptor>
</md:EntityDescriptor>

Attribute mapping

The attribute mapping is specified by AttributeExtractor in the shibboleth config:

linux # grep AttributeExtractor shibboleth2.xml 
   <AttributeExtractor type="XML" validate="true" reloadChanges="false" path="attribute-map.xml"/>

In my case keycloak uses urn:oasis:names:tc:SAML:2.0:attrname-format:basic for this SP, so I’ll match some of those attributes:

linux # cat /etc/shibboleth/attribute-map.xml
<?xml version="1.0"?>
<Attributes xmlns="urn:mace:shibboleth:2.0:attribute-map" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<...>
  <Attribute name="uid" nameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:basic" id="uid"/>
  <Attribute name="displayName" nameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:basic" id="displayName"/>
  <Attribute name="eduPersonAffiliation" nameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:basic" id="eduPersonAffiliation"/>
</Attributes>

Apache configuration

Now we need to include those settings into the apache vhost configuration:

<...>
   <Location />
       AuthType shibboleth
       ShibUseHeaders On
       Require valid-user
   </Location>
<...>

Please be aware, that there are more settings pre-configured in the apache module (we’ll come back to that in a minute):

linux # cat /etc/apache2/conf-enabled/shib.conf 
#
# Turn this on to support "require valid-user" rules from other
# mod_authn_* modules, and use "require shib-session" for anonymous
# session-based authorization in mod_shib.
#
ShibCompatValidUser Off

#
# Ensures handler will be accessible.
#
<Location /Shibboleth.sso>
  AuthType None
  Require all granted
</Location>

#
# Used for example style sheet in error templates.
#
<IfModule mod_alias.c>
  <Location /shibboleth-sp>
    AuthType None
    Require all granted
  </Location>
  Alias /shibboleth-sp/main.css /usr/share/shibboleth/main.css
</IfModule>

Mixed authentications

On my test system I was using different kinds of authentication in parallel. So when I finally got my first shibboleth login working it wasn’t long till I discovered that it broke (basic) authentication on the same system.

To trigger this I didn’t even have to use mod_shib: it was enough to just activate the apache module!

However to fix that I only had to set

<...>
ShibCompatValidUser On
<...>

in the apache config to make things work again.

Configuring identity provider

Now all we need to do is import our SP’s metadata into our identity provider (idp). In case of keycloak (the idp I currently use) you can just import the configuration as XML file.

The only bad news is: The XML created using shib-metagen has the wrong format (and only part of the required information).

So how/where do we get the correct XML?

Retrieving SP’s metadata

Most IdPs and SPs provide their metadata via some (more or less) well-known URL.

For shibboleth this URL is defined in /etc/shibboleth/shibboleth2.xml in the MetadataGenerator Handler section:

linux # grep MetadataGenerator /etc/shibboleth/shibboleth2.xml 
    <Handler type="MetadataGenerator" Location="/Metadata" signing="false"/>

The path /Metadata is relative to the handlerURL (default: “/Shibboleth.sso”), so for our setup the following curl command will fetch our SP’s metadata:

linux # curl https://www.mydomain.de/Shibboleth.sso/Metadata
<md:EntityDescriptor xmlns:md="urn:oasis:names:tc:SAML:2.0:metadata" ID="_df116724f8ecb7c2894095e4bc206b6a9324246d" entityID="https://www.mydomain.de/shib-protected">
<...>

Once logged in, mod_shib will provide several variables containing the shibboleth data:

_SERVER[HTTP_SHIB_HANDLER] = https://www.mydomain.de/Shibboleth.sso
_SERVEMixed authentications

On my test system I was using different kinds of authentication in parallel. So when I finally got my first shibboleth login working it wasn't long till I discovered that it broke (basic) authentication on the same system.

To trigger this I didn't even have to use mod_shib: it was enough to just activate the apache module!

However to fix that I only had to set

<...>
ShibCompatValidUser On
<...>

in the apache config to make things work again.

By default this is set to "Off" in /etc/apache2/conf-enabled/shib.conf.R[HTTP_SHIB_ASSERTION_COUNT] = 
_SERVER[HTTP_SHIB_AUTHNCONTEXT_DECL] = 
_SERVER[HTTP_SHIB_AUTHNCONTEXT_CLASS] = urn:oasis:names:tc:SAML:2.0:ac:classes:unspecified
_SERVER[HTTP_SHIB_AUTHENTICATION_INSTANT] = 2025-01-31T19:08:22.490Z
_SERVER[HTTP_SHIB_AUTHENTICATION_METHOD] = urn:oasis:names:tc:SAML:2.0:ac:classes:unspecified
_SERVER[HTTP_SHIB_IDENTITY_PROVIDER] = https://keycloak.mydomain.de/realms/MYDOMAIN
_SERVER[HTTP_SHIB_SESSION_INACTIVITY] = 1738354102
_SERVER[HTTP_SHIB_SESSION_EXPIRES] = 1738379302
_SERVER[HTTP_SHIB_SESSION_INDEX] = 12345678-9882-434a-8537-0461f7adce49::8adae372-60d9-44aa-b2d6-9d6912e612b2
_SERVER[HTTP_SHIB_SESSION_ID] = _12345678902c66dfbd627839e12922cb
_SERVER[HTTP_SHIB_COOKIE_NAME] = 
<...>
_SERVER[HTTPS] = on
_SERVER[uid] = testuser1
_SERVER[eduPersonAffiliation] = default-roles-mydomain;offline_access
_SERVER[displayName] = Testuser1
_SERVER[Shib-Session-Inactivity] = 1738354102
_SERVER[Shib-Session-Expires] = 1738379302
_SERVER[Shib-Session-Index] = 12345678-9882-434a-8537-0461f7adce49::8adae372-60d9-44aa-b2d6-9d6912e612b2
_SERVER[Shib-AuthnContext-Class] = urn:oasis:names:tc:SAML:2.0:ac:classes:unspecified
_SERVER[Shib-Authentication-Method] = urn:oasis:names:tc:SAML:2.0:ac:classes:unspecified
_SERVER[Shib-Authentication-Instant] = 2025-01-31T19:08:22.490Z
_SERVER[Shib-Identity-Provider] = https://keycloak.mydomain.de/realms/MYDOMAIN
_SERVER[Shib-Session-ID] = _12345678902c66dfbd627839e12922cb
_SERVER[Shib-Application-ID] = default
_SERVER[Shib-Handler] = https://www.mydomain.de/Shibboleth.sso

Side notes

Using apache IfModule statements with mod_shib

While trying to fix some side effects of mod_shib (s. above) I also had to modify some of my existing apache config files. However I wanted an easy way to restore the settings to the state before enabling mod_shib (in case I wanted to get rid of it some day). So I started to wrap mod_shib specific fixe into <IfModule> statements.

However, it turned out that <IfModule mod_shib.c> (copied and slightly modified from other places) didn’t do anything. So I started to dig around and found, that there are two ways to check for a module being loaded via <IfModule>: One is to use the “module identifier” the other one is the name of the source file it was compiled from.

For modules shipped with apache this is quite easy: Just take a look at the module listing, select your module and check the information about “Source File” and “Module Identifier” on top of that page.

But this does not help for external modules (like mod_shib). As it turns out shibboleth was programmed in cpp (and that’s why the correct source file is "mod_shib.cpp"). However I don’t like guessing, so I was looking for a way to easily determine the “module identifier” or the “source file” of an apache module. Turns out getting the “module identifier” is quite easy using apache’s apache2ctl tool:

linux # apache2ctl -M
Loaded Modules:
 core_module (static)
 so_module (static)
<...>
 access_compat_module (shared)
<...>
 mod_shib (shared)
<...>

See session content/state

After successful login you can display Shibboleth session information (via /Shibboleth.sso/Session URL on your SP):

Miscellaneous
Session Expiration (barring inactivity): 479 minute(s)
Client Address: 192.168.1.2
SSO Protocol: urn:oasis:names:tc:SAML:2.0:protocol
Identity Provider: https://keycloak.mydomain.de/realms/MYDOMAIN
Authentication Time: 2025-04-13T09:52:18.478Z
Authentication Context Class: urn:oasis:names:tc:SAML:2.0:ac:classes:unspecified
Authentication Context Decl: (none)

Attributes
eduPersonAffiliation: Allowed Users

Generating a new mod_shib configuration

Generating a new configuration for mod_shib from scratch:

linux # sudo apt install shibboleth-sp-utils
linux # mkdir testdir ; cd testdir
linux # shib-keygen -u $(id -u) -g $(id -g) -n sp1 -o .
linux # ls -1d *
sp1-cert.pem
sp1-key.pem
linux # chown _shibd:_shibd /etc/shibboleth/*.pem
linux # shib-metagen -c sp1-cert.pem -h www.mydomain.de/shib-protected 
 -e https://www.mydomain.de/shib-protected

Leave a Reply

Your email address will not be published. Required fields are marked *