OpenStack Federated Authentication

This document describes how to configure federated authentication on an OpenStack deployment leveraging SAML2 Identity Providers and an OpenID Connect Provider such as Google or Facebook.

The steps required to deploy OpenStack providing this Federated Authentication are performed by special versions of Juju charms available from the repository https://jujucharms.com:

  • cs:~csd-garr/keystone, controlled by a number of configuration parameters in its config.yaml file, e.g. enable_oidc and enable_saml2. For full details on the parameters and their use, see the README.md file in the bundle distribution.
  • cs:~csd-garr/openstack-dashboard. The config.yaml parameters saml2-idp and oidc-idp should be set to the names of the respective providers in order to enable Single Sign On through the dashboard.

More recently, the official Keystone charm charm has introduced the keystone-fid-service-provider interface, which allows subordinate charms, such as keystone-saml-mellon to contribute to the Keystone configuration in an elegant way. For more details please see https://cloud.garr.it/support/kb/cloud/federated_auth_juju/.

Overview

OpenStack authentication and authorization is managed by the Keystone component. Keystone is deployed as a WSGI instance behind a web front-end. In order to achieve a balanced and redundant setup, HAProxy manages the connection to the web front-end(s). Enabling federated authentication entails modification in the standard configuration of all the above parts, and of the end-user web application, the Dashboard. Federated authentication itself is provided by the Apache HTTP Server modules mod_shib2, for SAML2, and mod_auth_openidc, for OpenID Connect (OIDC).

See also

SAML2 will be used with Identity Providers belonging to Research and Education National Identity Federations through the eduGAIN inter-federation service. For more information about eduGAIN please refer to:

https://www.geant.org/Services/Trust_identity_and_security/eduGAIN

On the other hand, OIDC will be used with the Google OIDC Provider to account for all the users that have loose relationships with the R&E world.

Once all the above key components will be set up, it will be possible to configure actual Identity Providers for both SAML2 and OIDC, and upload and enable the federated users mapping. Let’s start.

See also

You can find detailed instructions on the setup of the federated authentication for OpenStack at the following location:

https://docs.openstack.org/developer/keystone/federation/federated_identity.html

Requirements

  • A OpenStack deployment on Debian/Ubuntu OS later than Mitaka
  • OpenStack components:
    • two or more Keystone instances;
    • Apache HTTP Server with the WSGI module to run Keystone;
    • HAProxy as load balancer in front of Apache;

Keystone Configuration

Federated authentication is (mostly) managed by Apache modules, so in order to enable SAML2 and OIDC authentication for the Keystone service some adjustments are needed in its configuration file.

Open /etc/keystone/keystone.conf and add/modify directives as instructed.

  • In the [auth] section the property methods should look like:

    methods = external,password,token,oauth1,oidc,saml2
    
  • In the [auth] section you should add:

    saml2 = keystone.auth.plugins.mapped.Mapped
    oidc = keystone.auth.plugins.mapped.Mapped
    
  • Add three new sections at the end of the file:

    [saml2]
    remote_id_attribute = Shib-Identity-Provider
    
    [oidc]
    remote_id_attribute = HTTP_OIDC_ISS
    
    [federation]
    trusted_dashboard = https://OPENSTACK_DASHBOARD_FQDN/auth/websso/
    

Warning

The above changes are dealt in the charm cs:~csd-garr/keystone through changes to file templates/ocata/keystone.conf, according to config paramaters enable_oidc and enable_saml2.

  • Finally, restart the Apache HTTP Server to enable the new configuration:

    $ service apache2 restart
    

Install and configure Apache modules

Install the Apache modules for SAML2 and OIDC authentication:

$ apt install libapache2-mod-shib2 libapache2-mod-auth-openidc

Shibboleth Service Provider

The mod_shib Apache module is little more than a bridge to the Shibboleth Service Provider deamon, shibd, which is installed along the way with the libapache2-mod-shib2 apt package, and which is the component that will speak SAML2 to the outside world. The daemon itself has to be configured independently by Apache.

See also

Please, note that while what follows will take you to a fully functional Shibboleth Service Provider, it is not a comprehensive installation howto. More information are available on the Shibboleth wiki:

https://wiki.shibboleth.net/confluence/display/SHIB2/Installation

You can also consult the IDEM Shibboleth Service Provider installation guide:

https://www.idem.garr.it/it/documenti/doc_download/313-installazione-shibboleth-service-provider-su-debian-linux

Start by creating the certificates that will be used by shibd:

$ cd /etc/shibboleth
$ shib-keygen -h FQDN -e SERVICE_PROVIDER_ENTITY_ID

Where FQDN is the Fully Qualified Domain Name of your Keystone VIP, and SERVICE_PROVIDER_ENTITY_ID is the SAML2 EntityID of your service provider, usually https://FQDN:5000/shibboleth.

Warning

Keystone VirtualHost by default will listen on port 5000, and the same goes for mod_shib that will run inside the very same VirtualHost. So, it is wise choice to have the entityID directly configured on port 5000.

The service provider needs to import the metadata of all the Identity Providers which will be enabled for authentication. A metadata aggregate containing all the eduGAIN entities and signed by the IDEM Identity Federations exists for that purpose. To validate it you should be able to verify the signature, so you have to download the signing public certificate:

$ wget https://www.idem.garr.it/documenti/doc_download/321-idem-metadata-signer-2019 -O /etc/shibboleth/idem_signer_2019.pem
$ chmod 444 /etc/shibboleth/idem_signer_2019.pem

See also

To publish your service and have it recognized by all the eduGAIN IdPs, you first need to register the Service Provider in a national R&E identity federation.

For the IDEM Federation registration procedures are available both in Italian and English:

[IT] https://www.idem.garr.it/come-partecipare

[EN] https://www.idem.garr.it/en/join

For other federations you can consult the Federation Page and Registration practice statement on the eduGAIN technical site:

https://technical.edugain.org/status

Next, you have to edit the file /etc/shibboleth/shibboleth2.xml to configure the SERVICE_PROVIDER_ENTITY_ID, setup a discovery service, and import the metadata aggregate.

entityID

To configure the entityID insert your SERVICE_PROVIDER_ENTITY_ID as an attribute of the <ApplicationDefaults> tag:

<ApplicationDefaults entityID="https://FQDN/shibboleth"
                     REMOTE_USER="eppn persistent-id targeted-id">

Discovery Service

A discovery service let users choose to which IdP they should be redirected for authentication. It goes without saying that the local discovery service that better integrates into Shibboleth Service Provider is the Shibboleth Embedded Discovery Service (EDS).

See also

For detailed information about EDS, please have a look at the Shibboleth Wiki:

https://wiki.shibboleth.net/confluence/display/EDS10/Embedded+Discovery+Service

Install EDS. Download the latest EDS distribution from the following link:

At the time of this writing the latest EDS distribution is 1.2.0, download it to /tmp:

$ cd /tmp
$ wget https://shibboleth.net/downloads/embedded-discovery-service/latest/shibboleth-embedded-ds-1.2.0.tar.gz

Untar/Unzip the distribution in the location from where the keystone web server is serving static content, for example /var/www/html/eds:

$ mkdir /var/www/html/eds
$ cd /tmp
$ tar -xfvz shibboleth-embedded-ds-1.2.0.tar.gz
$ cd shibboleth-embedded-ds-1.2.0
$ cp idpselect.js idpselect.css idpselect_config.js blank.gif index.html /var/www/html/eds

Now you can edit idpselect_config.js to setup language and customize the behaviour of the discovery (show IdP logos or not, define a helpURL, and the like). Remember, at least, to edit index.html to get rid of the pre-defined title, IDP select test bed.

Configure an Alias for EDS. You should configure an Alias in the Keystone VirtualHost to point to the EDS installation directory. In the example the alias will enable the discovery service at the URL https://FQDN:5000/eds. That URL will be eventually used to configure the discovery service in the Shibboleth Service Provider (see below).

Open the configuration file /etc/apache2/sites-enabled/wsgi-openstack-api.conf and add the Alias directive inside the <VirtualHost *:4990> instance:

[..]

<VirtualHost *:4990>
    ServerName https://FQDN:5000
    UseCanonicalName On

    Alias /eds /var/www/html/eds
[..]

Restart Apache to enable the new directive.

Configure the discovery service in Shibboleth Service Provider. Open the file /etc/shibboleth/shibboleth2.xml and add the attribute discoveryURL to the tag SSO with the URL of the local discovery service:

<SSO discoveryProtocol="SAMLDS"
     discoveryURL="https://FQDN:5000/eds">
     SAML2
</SSO>

Metadata

Configure a metadata provider in Shibboleth Service Provider.

Warning

Please note that configuring metadata is not enough for using the federated authentication. You must register in a R&E National Identity Federation, see above the section Shibboleth Service Provider for instructions.

Insert a new <MetadataProvider> with the eduGAIN metadata aggregate (in the example the aggregate is provided by the IDEM federation):

<MetadataProvider type="XML"
                  uri="https://www.garr.it/idem-metadata/edugain2idem-metadata-sha256.xml"
                  backingFilePath="edugain2idem-metadata-sha256.xml"
                  reloadIn-terval="7200">
    <MetadataFilter type="Signature" certificate="idem_signer_2019.pem"/>
    <MetadataFilter type="EntityRoleWhiteList">
        <RetainedRole>md:IDPSSODescriptor</RetainedRole>
        <RetainedRole>md:AttributeAuthorityDescriptor</RetainedRole>
    </MetadataFilter>
</MetadataProvider>

You should also enable some attribute definition and parsing. Open the file /etc/shibboleth/attribute-map.xml and uncomment the definitions of the following attributes:

<Attribute name="urn:mace:dir:attribute-def:cn" id="cn"/>
<Attribute name="urn:mace:dir:attribute-def:sn" id="sn"/>
<Attribute name="urn:mace:dir:attribute-def:givenName" id="givenName"/>
<Attribute name="urn:mace:dir:attribute-def:displayName" id="displayName"/>
<Attribute name="urn:mace:dir:attribute-def:mail" id="mail"/>

Finally there are a number of changes required in the Keystone Apache VirtualHost in order to enable the module and configure the endpoints. Open the /etc/apache/sites-enabled/wsgi-openstack-api.conf with an editor of your choice and follow the instruction below.

Enable the ShibCompatValidUser (necessary to make mod_shib works well with other Apache authZ modules), insert the following at the very beginning of the VirtualHost conf file:

ShibCompatValidUser On

Configure ServerName and enable canonical name in the <VirtualHost *:4990> instance:

ServerName https://FQDN:5000
UseCanonicalName On

Configure a WSGIScriptAlilas to call back the WSGI daemon for federated auth (again inside the <VirtualHost *:4990> instance):

WSGIScriptAlias ^(/v3/OS-FEDERATION/identity_providers/.*?/protocols/.*?/auth)$ /usr/bin/keystone-wsgi-public/$1

Add the Locations for the federated authZ and the Service Providers handlers (again inside the <VirtualHost *:4990> instance):

<Location /v3/OS-FEDERATION/identity_providers/idem-fed/protocols/saml2/auth>
    AuthType shibboleth
    ShibRequestSetting requireSession 1
    ShibExportAssertion Off
    Require valid-user
</Location>

<Location ~ "/v3/auth/OS-FEDERATION/websso/saml2">
    AuthType shibboleth
    ShibRequestSetting requireSession 1
    Require valid-user
</Location>

<Location ~ "/v3/auth/OS-FEDERATION/identity_providers/idem-fed/protocols/saml2/websso">
    AuthType shibboleth
    ShibRequestSetting requireSession 1
    Require valid-user
</Location>

<Location /Shibboleth.sso>
    SetHandler shib
</Location>

Once the main configuration of the Shibboleth Service Provider and of the Keystone VirtualHost is done, you can enable the mod_shib Apache module and restart the shibd service:

$ a2enmod shib2
$ service shibd restart

To check that shibd is working, and that at least the /Shibboleth.sso Location is correctly configured, try to access the Service Provider metadata at the following URL:

https://FQDN:5000/Shibboleth.sso/Metadata

If something goes wrong, have a look at the shibd logs:

$ less /var/log/shibboleth/shibd.log

OpenID Connect Resource Provider

The configuration of the mod_auth_openidc Apache module is a lot more straightfoward, since the module itself is more limited: it will let you configure just one OpenID Connect Provider (the Identity Provider) and there is no federation handling.

Warning

What follows is the configuration directives for the Google OpenID Connect Provider. In order to use the Google OIDC Provider, you have to register your client. To do so, please follow the Google instructions on:

https://developers.google.com/identity/protocols/OpenIDConnect

To enable the module we will not use the a2enmod command, but we will load it directly in the VirtualHost configuration file. In this way all the related configuration directives will be available in just one place. Open /etc/apache2/sites-enabled/wsgi-openstack-api.conf and add the following at the very beginning of the file:

LoadModule auth_openidc_module /usr/lib/apache2/modules/mod_auth_openidc.so

To configure the module add the following directives to the Keystone VirtualHost configuration file, /etc/apache2/sites-enabled/wsgi-openstack-api.conf, just before the Directory and the Locations directives inside the <VirtualHost *:4990> instance:

OIDCClaimPrefix "OIDC-"
OIDCResponseType "id_token"
OIDCScope "openid email profile"
OIDCProviderMetadataURL "https://accounts.google.com/.well-known/openid-configuration"
OIDCClientID "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
OIDCClientSecret "XXXXXXXXXXXXXXXXXXXX"
OIDCCryptoPassphrase "cryptopassphrase"
OIDCRedirectURI "https://FQDN:5000/v3/auth/OS-FEDERATION/websso/oidc/redirect"

Where:

  • OIDCClaimPrefix is a prefix that will be added to each OIDC claim (something to keep in mind when we will define the mapping).
  • OIDCResponseType defines the OpenID Connect authentication flow used.
  • OIDCScope defines the claims that will be returned by the OIDC Provider.
  • OIDCProviderMetadataURL is the URL from which the module will obtain all the OIDC Provider configuration details in json format (endpoints, supported flows, etc.). Here it is configured with the Google provided URL.
  • OIDCClientID is the client ID issued by the OIDC Provider during the client registration phase.
  • OIDCClientSecret is a secrete issued to the client by the OIDC Provider during the client registration phase.
  • OIDCCryptoPassphrase is a passphrase used to encrypt claims.
  • OIDCRedirectURI is a protected (by the module itself) URI that act as callback for the authentication response.

See also

The full documentation of mod_auth_openidc is available on github:

https://github.com/pingidentity/mod_auth_openidc

Once all the OIDCxxx directives are in place, you have to add all the protected Locations (again inside the <VirtualHost *:4990> instance):

OIDCRedirectURI "https://FQDN:5000/v3/auth/OS-FEDERATION/websso/oidc/redirect"

<Location /v3/OS-FEDERATION/identity_providers/google/protocols/oidc/auth>
    AuthType openid-connect
    Require valid-user
    LogLevel debug
</Location>

<Location ~ "/v3/auth/OS-FEDERATION/websso/oidc">
    AuthType openid-connect
    Require valid-user
    LogLevel debug
</Location>

<Location ~ "/v3/auth/OS-FEDERATION/identity_providers/google/protocols/oidc/websso">
    AuthType openid-connect
    Require valid-user
    LogLevel debug
</Location>

Finally, restart the Apache HTTP Server to enable the new configuration:

$ service apache2 restart

The Dashboard

The OpenStack Dashboard basic configuration for federated authentication is fairly short. Though, some additional steps are required if you also want to add some information about the service and the privacy policy (as required by many Research and Education Idenity Feeration standards and practices), a registration page, or you need to change the dashboard appearance through a custom python module.

Basic configuration

In the file /etc/openstack-dashboard/local_settings.py:

  • enable the Web Single Sign On:

    WEBSSO_ENABLED = True
    
  • add SAML2 and OIDC to the list of available authentication methods:

    WEBSSO_CHOICES = (
        ("credentials", _("Keystone Credentials")),
        ("saml2", _("Federazione IDEM")),
        ("oidc", _("Google")),
    )
    

Finally, restart the Apache HTTP Server to enable the new configuration:

$ service apache2 restart

Additional steps

Custom pages

If you need to add some custom pages to the Dashboard, the simplest way to do it, it is outside the Horizon django web app. So first of all, you should register a new Alias in order to set up a path that can be used to publish your pages.

Add an /info location to the file /etc/apache2/sites-enabled/default-ssl.conf, just before the end of the VirtualHost definition:

[..]
     Alias /info /var/www/info/
</VirtualHost>

This change must be applied to file templates/default-ssl.conf in the openstack-horizon charm.

Put the files in /var/www/info in order to make them published on the dashboard. Location for those pages will be /info/PAGENAME.

See also

Research and Education Identity Federations has standards and rules about the information page and the privacy policy of the services, the most important of it being the GEANT Data Protection Code of Conduct:

https://www.geant.org/uri/Pages/dataprotection-code-of-conduct.aspx

Login Page

The Dashboard login page might refer users to a registration page. You may edit the HTML of the Dashboard login page, for example add the following element:

<div class="help_text alert alert-info" style="display: block;">
        Don't have an account yet? Please
        <a href="REGISTRATION_SERVICE_URL">Register</a>.
</div>

Users that log succesfuly into their Identity provider but who do not have a valid account on the cloud platform, may be redirected to an error notfication page, that also provides a link to the same registration page.

On Keystone you need to add the following Apache configuration in order to handle the 401 Unauthorized access error from the Dashboard and to redirect the user to the notificatioin page. Create a file /etc/apache/conf-enabled/401.conf with the following content:

ErrorDocument 401 /401
Alias /401 /var/www/html/401.html
ProxyErrorOverride On

Create a redirection file /var/www/html/401.html, containing:

<!DOCTYPE html>
<html>
  <head>
    <meta http-equiv="refresh" content="0; url=/info/register.html" />
  </head>
  <body>
 <p>Display: <a href="/info/register.html">missing account error message.</a></p>
  </body>
</html>

and prepare the final notification file /var/www/html/register.html, containing:

<!DOCTYPE html>
<html>
<body>
        <h3>Unauthorized acccess.</h3>
        If you don't have an account, please
        <a href="REGISTRATION_SERVICE_URL">register</a>
        for using the service.
</body>
</html>

These file are created in the keystone charm, from template files templates/apache2/401.conf, templates/apache2/401.html. The redirection URL /var/www/html/register.html can actually be specified through config parameter no_user_mapping_url.

Finally, restart the Apache HTTP Server on the Keystone unit to enable the new configuration:

$ service apache2 restart

Hide Dashboard panels

If you need to change the appereance of the Dashboard, and more specifically if you want to hide some panels, you can do so by leveraging an horizon custom python module to override the default layout.

For example, if you want to let admin users to manage projects and members, but not other site-wide options you can clear the admin panel collection with the following python code:

###
### Hide admin panels from normal users.
###

import horizon

dashboard_slug = 'admin'

panel_slugs = [
    'instances',
    'volumes',
    'images',
    'aggregates',
    'info',
    'networks',
    'routers',
    'defaults',
    'hypervisors',
    'metadata_defs',
    'flavors',
    'metering'
]

def unregister_panel(dashboard_slug, panel_slug):
    dashboard = horizon.get_dashboard(dashboard_slug)
    panel = dashboard.get_panel(panel_slug)
    dashboard.unregister(panel.__class__)

for panel_slug in panel_slugs:
    unregister_panel(dashboard_slug, panel_slug)

According to the Horizon documentation`<https://docs.openstack.org/horizon/latest/configuration/customizing.html>_, to import this code into `Horizon, you need to:

  • save the above code to file: /usr/share/openstack-dashboard/openstack-dashboard/local/custom_settings.py

  • add the following to the file /etc/openstack-dashboard/local_settings.py:

    HORIZON_CONFIG["customization_module"] = "openstack-dashboard.local.custom_settings"
    
  • add a custom python-path to the WSGI daemon configuration in the file /etc/apache2/conf-enabled/openstack-dashboard.conf:

    WSGIDaemonProcess horizon user=horizon group=horizon processes=3 threads=10 python-path=/usr/share/openstack-dashboard
    

Finally, restart the Apache HTTP Server to enable the new configuration:

$ service apache2 restart

Users Mapping and Identity Providers

Having set up the Apache modules and the Keystone WSGI service, you are now ready to configure the users mapping and the identity Providers.

The federated authentication in Keystone supports two kind of users: ephemeral and local. The firsts are not mapped to local users, so they do not have to pre-exist, and once authenticated they will inherit all the configured roles on OpentStack projects through groups mapping. On the contrary with local users, the users should pre-exist, and all the roles and projects entitlements should be configured locally. What follows is based on local users.

Identity Providers

While it is true that federated authentication is technically provided by Apache modules, Keystone needs to know which Identity Providers (IdPs) it has to trust, and to which protocol they are related to. It goes by itself that in order to make it works with Identity Federation such as IDEM, or interfederation services like eduGAIN, you have to configure all the IdPs you want to enable.

Luckly enough, it is at least possible to insert a list of IdPs identified by one identifier.

To configure all the SAML2 IdPs you need to enable, prepare a file with a list of IdPs one per line, for example:

https://idp1/endpoint
https://idp2/endpoint

See also

If you want to configure all the Identity Providers of the IDEM Federation you can consult the eduGAIN Entities Database to download a CSV file containing all the needed entityIDs:

https://technical.edugain.org/entities

Save your file to /tmp/saml2_idp and load it into Keystone with the following command:

$ openstack identity provider create --remote-id-file /tmp/saml2_idp idem-fed

Warning

The OpenStack command above, and all the others that follows, must be given by a client configured with the OS_ADMIN_TOKEN or user authentication with domain admin or equivalent rights.

For Google OIDC obviously we have just one Identity Provider, so we just need to issue the following command:

$ openstack identity provider create --remote-id https://accounts.google.com google

Verify the list of IdPs associated to a provider with a command such as:

$ openstack identity provider show idem-fed | grep remote_ids | awk -F\| '{print $3}' | sed -e 's/^\s*//' -e 's/\s\+$//' -e 's/,\s*/\n/g' | sort > /tmp/listOfIdp.txt

Mapping

To configure the mapping between the federated users and the OpenStack ones, you have to prepare a JSON file with the desired mapping. In the example the SAML eduPersonPrincipalName (ePPN) attribute will be used as a key to match the local user name. For Google OIDC, the mail claim will be used.

Warning

Please note that the mail claim should be used if and only you’re certain that the user has only one mail. In all the other cases a combination of the sub and iss claims should be used to preserve both uniqueness and persistentcy.

For more information you should refere to:

http://openid.net/specs/openid-connect-core-1_0.html#ClaimStability

SAML2 JSON mapping:

[
  {
    "local": [
      {
        "user": {
          "domain": {
            "name": "{{ keystone.federated_user_domain }}"
            },
            "type": "local",
            "name": "{0}"
          }
      }
    ],
    "remote": [
      {
        "type": "eppn"
      }
    ]
  }
]

Save the file to /tmp/saml2_mapping.json and load it into the Keystone with the following command:

$ openstack mapping create --rules /tmp/saml2_mapping.json saml2_mapping

Now we have to do the same for OpenID Connect.

OIDC JSON mapping:

[
  {
    "local": [
      {
        "user": {
          "domain": {
            "name": "federated_users"
          },
            "type": "local",
            "name": "{0}"
        }
      }
    ],
    "remote": [
      {
        "type": "OIDC-email"
      }
    ]
  }
]

Save the file to /tmp/oidc_mapping.json and load it into the Keystone with the following command:

$ openstack mapping create --rules /tmp/oidc_mapping.json oidc_mapping

It remains one final step.

Protocols

Now you have to join the configured Identity Providers to the mappings for each protocol.

To configure the SAML2 protocol gives the following command:

$ openstack federation protocol create saml2 --mapping saml2_mapping --identity-provider idem-fed

And for the OIDC protocol:

$ openstack federation protocol create oidc --mapping oidc_mapping --identity-provider google

Troubleshooting

If you are wondering how many things can go south, just have a look at this list of log files you can check for warnings and errors:

  • Keystone
    • /var/log/apache2/keystone.log
    • /var/log/apache2/keystone/keystone.log
  • Dashboard
    • /var/log/apache2/error.log

Needless to say, the more instances of Keystone and Dashboard you have, the more urgent the adoption of a centralized syslog solution becomes.