Shibboleth IDP3 + SP + jetty9 + nginx sobre Debian

Objectiu:

Fa uns anys vaig fer una sèrie d’entrades sobre com instal·lar Shibboleth 2.x. Això ha quedat una mica desfasat i, a la vegada, els protocols han evolucionat. La idea d’aquesta entrada és actualitzar el procediment d’instal·lació de la versió 3.6 de l’IDP de Shibboleth i disposar d’un SP que autentiqui via IDP + LDAP i obtingui atributs de l’IDP.

Procediment:

El procediment no és senzill i hi ha moltes coses que poden anar malament. A la vegada, ara que no ens entenen els de Shibboleth… no he vist mai documentació més feixuga i pesada de seguir. La veritat és que no sé quin és el seu usuari tipus, però si no es disposa de tres doctorats i dos màsters es fa molt difícil seguir el que ells consideren un howto bàsic. Dit això, preparem-nos per embrutar-nos i som-hi…

Ingredients necessaris:

Per fer aquesta instal·lació necessitarem:

  • Dues màquines virtuals, una per a l’IDP3 (amb Debia 9, jetty9 i nginx) i una per a l’SP (amb una aplicació web sobre Apache+PHP7, aquesta amb Debian 9 o superior).
  • Les dues màquines amb sistema operatiu sense entorn gràfic i amb els paquets estàndard del sistema únicament (això és opcional, però recomanable en entorns de producció).
  • La màquina de l’IDP s’anomenarà idp3.dom.lan amb IP 192.168.0.1/24
  • La màquina de l’SP s’anomenarà sp1.dom.lan amb IP 192.168.0.2/24
  • La configuració de l’arxiu /etc/hosts reflectirà aquestes dues anteriors configuracions de noms. Això ho farà portable en aquest exemple. En un entorn de producció real aquesta configuració la faríem, probablement al servidor DNS de l’empresa.

Aquest és un exemple de l’arxiu /etc/hosts (vàlid per a les dues màquines):

$ pico /etc/hosts
127.0.0.1    localhost

192.168.0.1  idp3.dom.lan sp1.dom.lan

The following lines are desirable for IPv6 capable hosts
::1     localhost ip6-localhost ip6-loopback
ff02::1 ip6-allnodes
ff02::2 ip6-allrouters

Primer pas: Instal·lar jetty9 a la màquina virtual de l’IDP

Com a servidor d’aplicacions utilitzarem jetty9. La versió 9.4 està disponible als repositoris de Debian. Això instal·larà també java8 en la versió openjdk, necessari per a l’execució del servidor:

$ apt install jetty9
$ pico /etc/environment
JAVA_HOME=/usr/lib/jvm/default-java

Podem verificar que la instal·lació s’ha dut a terme correctament accedint a la pàgina web: http://idp3.dom.lan:8080. Hauríem de veure la pàgina per defecte de jetty9.

En la configuració de jetty9 afegirem els següents canvis:

$ pico /etc/jetty9/start.d/start.ini
--module=http-forwarded

$ pico /etc/jetty9/jetty.xml
<Call name="addCustomizer">
     <Arg><New class="org.eclipse.jetty.server.ForwardedRequestCustomizer"/></Arg>
</Call>

Segon pas: Instal·lar nginx i configurar proxy SSL/TLS cap a jetty9

Com a servidor proxy de peticions al jetty9 i, a la vegada, com a TLS/SSL terminator utilitzarem nginx. De nou, la instal·lació és senzilla ja que els paquets necessaris estan disponibles per a Debian:

$ apt install nginx

Podem verificar que la instal·lació ha anat correctament accedint a la pàgina: http://idp3.dom.lan. Hauríem de veure la pàgina per defecte de nginx.

En aquest punt és important notar que jetty9 escolta al port 8080 i que nginx escolta en el port 80. Cap dels dos està configurat encara per acceptar peticions segures mitjançant SSL/TLS al port 443.

Ens interessa que totes les peticions SSL/TLS es facin a l’nginx i que aquest les reenviï al jetty9. La configuració, doncs, per a nginx serà la següent (l’arxiu no existeix i s’ha de crear):

$ pico /etc/nginx/conf.d/default.conf
upstream idp3 { server localhost:8080; }

server {
     listen 0.0.0.0:80;
     listen 0.0.0.0:443 ssl;

     server_name  idp3.dom.lan;

     if ($ssl_protocol = "") {
         rewrite ^ https://$server_name$request_uri? permanent;
     }

     ssl on; 
     ssl_certificate /etc/nginx/ssl/dom.lan.crt;
     ssl_certificate_key /etc/nginx/ssl/dom.lan.key; 

     location / {  
         proxy_pass http://idp3;  
         proxy_next_upstream error timeout invalid_header http_500 http_502 http_503 http_504;
         proxy_redirect off;
         proxy_buffering off;  
         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 https;
         proxy_set_header        X-Scheme $scheme; 
    }
}

Finalment reiniciem nginx:

$ service nginx restart

IMPORTANT: En aquesta configuració es defineixen les rutes dels arxius de certificat i clau que s’utilitzaran per a la configuració SSL/TLS. S’han utilitzat certificats wildcard *.dom.lan perquè simplifiquen la gestió de certificats. Aquests arxius es poden obtenir via openssl, via Let’s Encrypt, o a partir de certificats vàlids de l’empresa. Sigui com sigui aquests són els certificats que veurà el client quan connecti a idp3.dom.lan.

En aquest punt podem accedir a https://idp3.dom.lan i hauríem de veure la pàgina d’inici de jetty9 mitjançant una connexió segura SSL/TLS.

Tercer pas: Instal·lar Shibboleth IDP3 amb LDAP

En aquest punt estem preparats per instal·lar Shibboleth IDP. L’arxiu amb el programari necessari el descarregarem de la pàgina de Shibboleth. Per aquest tutorial aquest és l’arxiu que s’ha utilitzat.

La instal·lació s’executa amb l’arxiu bin/install.sh. A continuació s’ha marcat en negreta els camps entrats a les preguntes de l’instal·lador.

$ cd /root/
$ wget https://shibboleth.net/downloads/identity-provider/3.4.6/shibboleth-identity-provider-3.4.6.tar.gz
$ tar zxf shibboleth-identity-provider-3.4.6.tar.gz
$ cd shibboleth-identity-provider-3.4.6/
$ bin/install.sh
Source (Distribution) Directory (press  to accept default): [/root/shibboleth-identity-provider-3.4.6]
Installation Directory: [/opt/shibboleth-idp]
Hostname: [localhost.localdomain]
idp3.dom.lan
SAML EntityID: [https://idp3.dom.lan/idp/shibboleth]
Attribute Scope: [localdomain]
dom.lan
Backchannel PKCS12 Password: *****
Re-enter password: *****
Cookie Encryption Key Password: *****
Re-enter password: *****
Warning: /opt/shibboleth-idp/bin does not exist.
Warning: /opt/shibboleth-idp/edit-webapp does not exist.
Warning: /opt/shibboleth-idp/dist does not exist.
Warning: /opt/shibboleth-idp/doc does not exist.
Warning: /opt/shibboleth-idp/system does not exist.
Generating Signing Key, CN = idp3.dom.lan URI = https://idp3.dom.lan/idp/shibboleth ...
...done
Creating Encryption Key, CN = idp3.dom.lan URI = https://idp3.dom.lan/idp/shibboleth ...
...done
Creating Backchannel keystore, CN = idp3.dom.lan URI = https://idp3.dom.lan/idp/shibboleth ...
...done
Creating cookie encryption key files...
...done
Rebuilding /opt/shibboleth-idp/war/idp.war ...
...done
BUILD SUCCESSFUL
Total time: 35 seconds

$ cd /opt
$ chown jetty:jetty -R shibboleth-idp

Fet això, podem indicar a jetty9 on es troba la nova aplicació a desplegar:

$ pico /var/lib/jetty9/webapps/idp.xml
<Configure class="org.eclipse.jetty.webapp.WebAppContext">
<Set name="war">/opt/shibboleth-idp/war/idp.war</Set>
    <Set name="contextPath">/idp</Set>
    <Set name="extractWAR">false</Set>
    <Set name="copyWebDir">false</Set>
    <Set name="copyWebInf">true</Set>
    <Set name="persistTempDirectory">false</Set>
</Configure>

Les metadades inicials configurades per l’instal·lador de l’IDP posen la data de validesa a la data actual, cosa que no és correcte. S’ha de modificar l’arxiu /opt/shibboleth-idp/metadata/idp-metadata.xml i actualitzar aquest valor:

$ pico /opt/shibboleth-idp/metadata/idp-metadata.xml
...
<EntityDescriptor  xmlns="urn:oasis:names:tc:SAML:2.0:metadata" xmlns:ds="http://www.w3.org/2000/09/xmldsig#" xmlns:shibmd="urn:mace:shibboleth:metadata:1.0" xmlns:xml="http://www.w3.org/XML/1998/namespace" xmlns:mdui="urn:oasis:names:tc:SAML:metadata:ui" xmlns:req-attr="urn:oasis:names:tc:SAML:protocol:ext:req-attr" validUntil="2039-11-13T15:47:11.916Z" entityID="https://idp3.dom.lan/idp/shibboleth">
...

Finalment per acabar la configuració inicial de Shibboleth IDP, configurarem l’LDAP com a backend d’autenticació. Aquesta versió de Shibboleth IDP ho porta gairebé tot fet. Només queda editar l’arxiu /opt/shibboleth-idp/conf/ldap.properties. A continuació només es mostren els valors que s’han modificat de l’arxiu original:

$ pico /opt/shibboleth-idp/conf/ldap.properties
idp.authn.LDAP.authenticator = bindSearchAuthenticator
idp.authn.LDAP.ldapURL = ldap://ldap.dom.lan idp.authn.LDAP.useStartTLS = true
idp.authn.LDAP.useSSL = false

idp.authn.LDAP.sslConfig = jvmTrust
idp.authn.LDAP.returnAttributes = uid,mail,givenName,sn

idp.authn.LDAP.baseDN = dc=dom,dc=lan
idp.authn.LDAP.subtreeSearch = true
idp.authn.LDAP.userFilter = (uid={user})
idp.authn.LDAP.bindDN = cn=ldapuser,cn=users,dc=dom,dc=lan
idp.authn.LDAP.bindDNCredential = bindpassword

En l’arxiu /opt/shibboleth-idp/conf/access-control.xml afegim el rang d’adreces des d’on connectem per verificar l’estat (status) del servidor IDP.

$ pico /opt/shibboleth-idp/conf/access-control.xml
...
<entry key="AccessByIPAddress">
    <bean id="AccessByIPAddress" parent="shibboleth.IPRangeAccessControl"
    p:allowedRanges="<{ {'127.0.0.1/32', '::1/128', '192.168.0.0/24'} }" />
</entry>
...

En aquest punt podem reiniciar jetty9 i l’aplicació es desplegarà:

$ service jetty9 restart

En aquest punt hauríem de poder accedir a l’adreça https://idp3.dom.lan/idp i veure una pantalla de Shibboleth IDP informant-nos que actualment no hi ha serveis configurats. Això és normal. A la vegada la pàgina https://idp3.dom.lan/idp/status ens mostrarà l’estat de l’IDP.

Quart pas: Instal·lar un SP

Amb l’IDP llest és el moment de configurar un SP (Service Provider) que contacti amb l’IDP per autenticar usuaris via LDAP.

En la màquina per a l’SP instal·larem apache2, php i el mòdul shib2 (tot i no especificar que volem l’apache2 aquest s’instal·larà igual perquè es tracta d’una dependència de php):

$ apt install libapache2-mod-php libapache2-mod-shib2

A continuació deshabilitarem la pàgina per defecte i instal·larem la nova amb una aplicació php que autentiqui amb l’IDP de Shibboleth:

$ a2dissite 000-default.conf
$ pico /etc/apache2/site-available/sp1.conf
<VirtualHost *:80>
    ServerName https://sp1.dom.lan:443
    UseCanonicalName on
    DocumentRoot /var/www/html

    <Directory /var/www/html>
        AuthType shibboleth
        ShibRequireSession On
        require shibboleth
    </Directory>

    ErrorLog ${APACHE_LOG_DIR}/error.log
    CustomLog ${APACHE_LOG_DIR}/access.log combined
</VirtualHost>

$ a2ensite sp1.conf
$ service apache2 restart

Com es pot veure a la configuració l’accés sempre es farà via https utilitzant SSL/TLS. En aquest exemple el nostre SSL/TLS terminator és l’nginx que hem configurat en la màquina IDP. A continuació actualitzem la configuració per afegir aquest nou servei web:

$ cat /etc/nginx/conf.d/default.conf
upstream idp3 { server localhost:8080; }
upstream sp1 { server 192.168.0.2:80; }

server {
     listen 0.0.0.0:80;
     listen 0.0.0.0:443 ssl;

     server_name  idp3.dom.lan;

     if ($ssl_protocol = "") {
         rewrite ^ https://$server_name$request_uri? permanent;
     }

     ssl on; 
     ssl_certificate /etc/nginx/ssl/dom.lan.crt;
     ssl_certificate_key /etc/nginx/ssl/dom.lan.key; 

     location / {  
         proxy_pass http://idp3;  
         proxy_next_upstream error timeout invalid_header http_500 http_502 http_503 http_504;
         proxy_redirect off;
         proxy_buffering off;  
         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 https;
         proxy_set_header        X-Scheme $scheme; 
    }
}

server {
     listen 0.0.0.0:80;
     listen 0.0.0.0:443 ssl;

     server_name  sp1.dom.lan;

     if ($ssl_protocol = "") {
         rewrite ^ https://$server_name$request_uri? permanent;
     }

     ssl on; 
     ssl_certificate /etc/nginx/ssl/dom.lan.crt;
     ssl_certificate_key /etc/nginx/ssl/dom.lan.key; 

     location / {  
         proxy_pass http://sp1;  
         proxy_next_upstream error timeout invalid_header http_500 http_502 http_503 http_504;
         proxy_redirect off;
         proxy_buffering off;  
         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 https;
         proxy_set_header        X-Scheme $scheme; 
    }
}

A continuació configurarem el mòdul shib2. Els arxius de configuració es troben a la carpeta /etc/shibboleth. Primer crearem el certificat i la clau necessaris per a la comunicació amb l’IDP.

$ shib-keygen -f -u _shibd -h sp1.dom.lan -y 10 -e https://sp1.dom.lan/shibboleth -o /etc/shibboleth/

A continuació modificarem l’arxiu /etc/shibboleth/shibboleth.xml tot especificant l’adreça de l’IDP (només es mostren els canvis específics):

$ pico /etc/shibboleth/shibboleth2.xml
...
<ApplicationDefaults entityID="https://sp1.dom.lan/shibboleth" />
...
<SSO entityID="https://idp3.dom.lan/idp/shibboleth" discoveryProtocol="SAMLDS" discoveryURL="https://ds.example.org/DS/WAYF">
    SAML2
</SSO>
...
<MetadataProvider type="XML" url="https://idp3.dom.lan/idp/shibboleth" backingFilePath="idp-metadata.xml" reloadInterval="7200">
</MetadataProvider>
...
<CredentialResolver type="File" key="sp-key.pem" certificate="sp-cert.pem"/>
...

És important notar que ja no utilitzem el protocol depreciat SAML1. En aquest punt es poden reiniciar els serveis shibd i apache2:

$ service shibd restart && service apache2 restart

A continuació s’ha de configurar l’IDP, de nou, per a afegir aquest nou SP com a servei disponible a l’autenticació. Això, en la màquina de l’IDP, ho farem modificant l’arxiu /opt/shibboleth-idp/conf/metadata-providers.xml:

$ cat /opt/shibboleth-idp/conf/metadata-providers.xml
...
<MetadataProvider id="sp1" xsi:type="FileBackedHTTPMetadataProvider" backingFile="%{idp.home}/metadata/sp1-metadata.xml" metadataURL="https://sp1.dom.lan/Shibboleth.sso/Metadata">
</MetadataProvider>
...

$ service jetty9 restart

Si tot ha anat bé ja podem accedir a l’adreça https://sp1.dom.lan i hauríem de ser redirigits a l’IDP per autenticar-nos. Després d’entrar el nom d’usuari i la contrasenya hauríem de ser redirigits a la pàgina d’inici d’apache2, tal i com hem configurat.

Cinquè pas: Pas d’atributs bàsics

Fins aquest punt tenim un IDP 3 configurat i un SP que pot autenticar recursos. En aquesta secció configurarem l’IDP perque enviï atributs a l’SP. En concret enviarem 4 atributs: uid, givenname, sn i mail retornats pel servidor LDAP.

A la màquina on hi ha l’SP configurem el següent:

$ pico /etc/shibboleth/attribute-map.xml
...
<Attribute name="urn:mace:dir:attribute-def:uid" id="uid" />
<Attribute name="urn:oid:0.9.2342.19200300.100.1.1" id="uid" />

<Attribute name="urn:mace:dir:attribute-def:givenName" id="givenName" />
<Attribute name="urn:oid:2.5.4.42" id="givenName" />

<Attribute name="urn:mace:dir:attribute-def:sn" id="sn" />
<Attribute name="urn:oid:2.5.4.4" id="sn" />

<Attribute name="urn:mace:dir:attribute-def:mail" id="mail" />
<Attribute name="urn:oid:0.9.2342.19200300.100.1.3" id="mail" />
...

I, finalment, a la configuració de l’IDP canviarem l’arxiu /opt/shibboleth-idp/attribute-resolver.xml per afegir el mapeig d’atributs obtinguts de l’LDAP:

$ cd /opt/shibboleth-idp/conf
$ mv attribute-resolver.xml attribute-resolver-original.xml
$ mv attribute-resolver-ldap.xml attribute-resolver.xml
$ pico /opt/shibboleth-idp/conf/attribute-resolver.xml
...
<AttributeDefinition id="givenName" xsi:type="Simple">
    <InputDataConnector ref="myLDAP" attributeNames="givenName"/>
    <AttributeEncoder xsi:type="SAML1String" name="urn:mace:dir:attribute-def:givenName" encodeType="false" />
    <AttributeEncoder xsi:type="SAML2String" name="urn:oid:2.5.4.42" friendlyName="givenName" encodeType="false" />
</AttributeDefinition>

<AttributeDefinition id="sn" xsi:type="Simple">
    <InputDataConnector ref="myLDAP" attributeNames="sn"/>
    <AttributeEncoder xsi:type="SAML1String" name="urn:mace:dir:attribute-def:sn" encodeType="false" />
    <AttributeEncoder xsi:type="SAML2String" name="urn:oid:2.5.4.4" friendlyName="sn" encodeType="false" />
</AttributeDefinition>
...
### Eliminar la línia <trustFile="%{idp.attribute.resolver.LDAP.trustCertificates}", nosaltres fem serveir jvmTrust i no tenim el certificat del servidor LDAP.

Els atributs mail i uid ja són a la configuració per defecte, pel que no cal afegir-los. Ara que hem definit quins són els atributs que volem passar hem d’establir les regles de a qui els volem passar. Això ho farem modificant l’arxiu /opt/shibboleth-idp/attribute-filter.xml:

$ pico /opt/shibboleth-idp/conf/attribute-filter.xml
...
<AttributeFilterPolicy id="sp1">
    <PolicyRequirementRule xsi:type="OR">
        <Rule xsi:type="Requester" value="https://sp1.uda.ad" />
        <Rule xsi:type="Requester" value="https://sp1.uda.ad/shibboleth" />
    </PolicyRequirementRule>

    <AttributeRule attributeID="uid">
        <PermitValueRule xsi:type="ANY" />
    </AttributeRule>

    <AttributeRule attributeID="sn">
        <PermitValueRule xsi:type="ANY" />
    </AttributeRule>
    
    <AttributeRule attributeID="givenName">
        <PermitValueRule xsi:type="ANY" />
     </AttributeRule>

     <AttributeRule attributeID="mail">
         <PermitValueRule xsi:type="ANY" />
     </AttributeRule>
</AttributeFilterPolicy>
...

En aquest punt està tot llest. Podem reiniciar jetty9 i, després d’autenticar-nos de nou al servei, podem accedir a la pàgina https://sp1.dom.lan/Shibboleht.sso/Session i veure si els atributs s’han passat correctament. Altrament, també podem mostrar la variable $_SERVER de php i veure quines variables ha creat shibboleth.

Pas opcional: Deshabilitar el consentiment de pas d’atributs

En l’entorn local que estem configurant potser no és necessari el consentiment dels usuaris en el pas d’atributs entre IDP i SP. Això és pot deshabilitar a l’IDP modificant l’arxiu /opt/shibboleth-idp/conf/relying-party.xml:

$ pico /opt/shibboleth-idp/conf/relying-party.xml
### Eliminar p:postAuthenticationFlows="attribute-release" allà on aparegui

Finalment, com sempre, reiniciar el servidor jetty9.