About

Helix SAML Authentication
  • 3
    Members
  • 1
    Follower
  • 1
    Branch
Owners
nathan_fiedler (Nathan Fiedler)perforce_software (perforce_software)ttyler (ttyler)
Members
nathan_fiedler (Nathan Fiedler)perforce_software (perforce_software)ttyler (ttyler)
Followers
joel_brown (Joel Brown)
Branches
  • Main

SAML Triggers

The SAML support in Helix requires two parts: 1) the desktop agent that handles the SAML login request with the identity provider (IdP), and 2) the triggers that facilitate initiating the request and processing the response. This document is concerned with the installation and configuration of those triggers.

Superseded Technology

This authentication integration solution has been superseded by the Helix Authentication Extension and Helix Authentication Service which support both the SAML 2.0 and OpenID Connect protocols, and do so without the need for a desktop agent.

We strongly advise that customers use the Helix Authentication Service instead of Helix SAML, as Helix SAML will be end-of-life for standard maintenance as of 2019-12-13 and end-of-life for extended maintenance as of 2020-12-13.

Requirements

  • Python 2.7 or Python 3.6+
  • libxml2 and xmlsec libraries
  • Compiler tools (e.g. gcc)
  • libtool library (ltdl)

Installation

The triggers are written in Python and rely on the onelogin/python3-saml Python module, which in turn requires several other modules, including native libraries. The Python modules can all be installed using the pip utility, which can be installed like so:

$ curl https://bootstrap.pypa.io/get-pip.py -o get-pip.py
$ python get-pip.py

Instructions for installation without using pip are available below.

The required native libraries are libxml2 and xmlsec. These are usually available as packages on the popular Linux distributions. Examples are given below for Ubuntu Linux.

Python 2.7

Depending on your operating system, there is distribution-specific preliminary steps, followed by the platform-independent Python module installation.

CentOS 6

Installing Python 2.7 on CentOS 6 involves using the Software Collections, and the related scl tool. Some of the Python modules require the compiler tools, and so we install the development tools group. The ltdl library is used for building the modules.

$ sudo yum groupinstall -y "development tools"
$ sudo yum install libtool-ltdl-devel
$ sudo yum install centos-release-scl
$ sudo yum install python27 python27-python-devel python27-python-virtualenv
$ sudo yum install libxml2-devel xmlsec1-devel xmlsec1-openssl
$ scl enable python27 bash

Ubuntu 16

$ sudo apt-get install python2.7 python2.7-dev python-virtualenv
$ sudo apt-get install libxml2-dev libxmlsec1-dev libxmlsec1-openssl

Common

$ python -m virtualenv --python=python2.7 pysaml
$ source pysaml/bin/activate
$ pip install --upgrade pip
$ pip install -r requirements.txt

This example creates a Python virtual environment, which is not required, but helps to keep the dependencies isolated from the rest of the system.

Python 3.x

CentOS 6

Installing Python 3.6 on CentOS 6 involves using the Software Collections, and the related scl tool. Some of the Python modules require the compiler tools, and so we install the development tools group. The ltdl library is used for building the modules.

$ sudo yum groupinstall -y "development tools"
$ sudo yum install libtool-ltdl-devel
$ sudo yum install centos-release-scl
$ sudo yum install rh-python36 rh-python36-python-devel rh-python36-python-virtualenv
$ sudo yum install libxml2-devel xmlsec1-devel xmlsec1-openssl
$ scl enable rh-python36 bash

Ubuntu 16

$ sudo apt-get install python3 python3-dev python3-virtualenv
$ sudo apt-get install libxml2-dev libxmlsec1-dev libxmlsec1-openssl

Common

$ python3 -m virtualenv --python=python3 pysaml
$ source pysaml/bin/activate
$ pip install --upgrade pip
$ pip install -r requirements.txt

This example creates a Python virtual environment, which is not required, but helps to keep the dependencies isolated from the rest of the system.

Installing without using pip

The Python dependencies can be installed without the use of pip, if, for instance, your security policies require reviewing the software prior to installation.

Prerequisites

To install the Python modules from source, you will need to have the setuptools module installed. If you create a Python virtual environment, setuptools will be provided automatically.

Installation Procedure

To start, download the compressed source for each of the required modules:

  • https://pypi.org/project/requests/
  • https://pypi.org/project/six/
  • https://pypi.org/project/lxml/
  • https://pypi.org/project/xmlsec/
  • https://pypi.org/project/isodate/
  • https://pypi.org/project/defusedxml/
  • https://pypi.org/project/python3-saml/

Next, extract each of those in turn, running the following sequence of commands, replacing module with the appropriate module name and version:

$ tar zxf module.tar.gz
$ cd module
$ python setup.py build
$ python setup.py install

Configuration

The SAML configuration consists of two parts, that of the identity provider (IdP), and that of the service provider (SP). The service provider in this scenario is these trigger scripts; they act as the service provider, creating the login request URL and validating the SAML response. The configuration for the service provider is defined in settings.json and advanced_settings.json, which are described in more detail below. These two files should be in the same directory as the Python triggers. Most of the settings should be familiar to those accustomed to SAML configuration. There is additional information on the python3-saml project page.

Service Provider

The settings related to the definition of the service provider must match those found in the application configuration within the identity provider. That is, both the trigger configuration and the IdP must agree on the URLs, signing keys, issuer name, and so on.

For instructions specific to Okta, see the docs/Okta.md document for additional guidance on configuring the application within the Okta administration interface.

Identity Provider

As for the IdP configuration, there are two choices: the easiest is to use the IdP metadata URL, from which the triggers will request the IdP configuration. The second option is to configure the IdP via the settings.json file. The advantage of using the URL is that the IdP may change its certificate from time to time, and that is automatically conveyed via the metadata. The advantage of using the settings file is that the trigger does not need to fetch a resource from the IdP every time a user logs in.

Using the metadata URL

Usually the IdP web site will provide a URL for its configuration, referred to as "metadata". Find that URL and use it to set the auth.sso.args Perforce configuration setting, then add %ssoArgs% to the trigger entry in Perforce.

For example:

$ p4 configure set auth.sso.args=--idpUrl=http://192.168.24.3:7000/metadata

$ p4 triggers -o
Triggers:
    saml-pre auth-pre-sso auth "/ps/pysaml/bin/python /ps/saml_pre_sso.py %ssoArgs% %email%"
    saml-sso auth-check-sso auth "/ps/pysaml/bin/python /ps/saml_validate.py %ssoArgs% %email%"
    saml-slo auth-invalidate auth "/ps/pysaml/bin/python /ps/saml_logout.py %ssoArgs% %email%"

Using the settings file

Add something like the following to the settings.json file in order to define the IdP configuration. Note that this file is in JSON format, so be sure to use double-quotes for both names and values, and add commas between name/value pairs. The "idp" definition is at the same level as the "sp" definition that already exists in the settings file.

{
    "sp": {
        ...
    },
    "idp": {
        "entityId": "urn:example:idp",
        "singleSignOnService": {
            "url": "http://192.168.24.3:7000/saml/sso",
            "binding": "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect"
        },
        "singleLogoutService": {
            "url": "http://192.168.24.3:7000/saml/slo",
            "binding": "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect"
        },
        "certFingerprint": "",
        "certFingerprintAlgorithm": "sha1",
        "x509cert": "-----BEGIN CERTIFICATE-----\n!!FILL IN THIS BLANK!!\n-----END CERTIFICATE-----\n"
    }
}

Additional information regarding the settings file is available on the python3-saml project page.

Certificates

If the identity provider expects login and/or logout requests to be signed by the service provider, then the X.509 certificates should be installed in a directory named certs, in the same directory as the Python triggers. The files are named sp.crt and sp.key (public certificate and private key, respectively). To create self-signed certificates valid for one year, use the openssl command, like so:

$ openssl req -new -x509 -days 365 -nodes -out sp.crt -keyout sp.key

Configure non-SSO access

Before installing the SSO triggers, make sure that you have administrative access that does not require using SSO. Once the SSO triggers are installed, all authentication will require using SSO, even for administrators.

One approach would be to create a Perforce group whose Timeout is unlimited, then add the administrator to that group, and then login as the administrator to acquire the new long-lived ticket.

Another approach would be to enable non-SSO logins, by setting the auth.sso.allow.passwd configurable to 1; note, however, that once this is enabled, every Perforce user must have a password defined in the server. This is normally the case when the security level is set to 3 or higher.

After making changes to the security settings, you will need to restart the server (e.g. p4dctl stop despot and p4dctl start despot).

Numeric User Identifiers

Helix Server 2018.2 and later support numeric user identifiers. To enable this feature, use the command p4 configure set dm.user.numeric=1 as a super/admin user, and then restart the server (p4 admin restart) for the change to take effect.

Defining the Triggers

Once the trigger scripts are in place, and the configuration files have been updated, you can define the trigger entries in Helix Server. They should look similar to the following, replacing the paths to python and the triggers with whatever is appropriate for your system:

saml-pre auth-pre-sso auth "/path/pysaml/bin/python /path/saml_pre_sso.py %ssoArgs% %email%"
saml-sso auth-check-sso auth "/path/pysaml/bin/python /path/saml_validate.py %ssoArgs% %email%"
saml-slo auth-invalidate auth "/path/pysaml/bin/python /path/saml_logout.py %ssoArgs% %email%"

This example assumes that the SAML name identifier is the user's email address, hence the %email%. If instead the IdP is using a username, and the Perforce user names match what is configured in the IdP, then change the %email% to %user% in the example above.

After adding the triggers, you will need to restart the server using the command p4 admin restart as a Perforce super user. Note that this works also with p4d running in a Docker container, without the need to restart the container.

Logout with Okta

If your IdP services are provided by Okta then you will want to adjust the arguments to the saml_logout.py script slightly, by adding --okta between saml_logout.py and %ssoArgs% in the trigger table entry. This informs the trigger to use the Okta API to perform the logout, as that works better for our scenario which is operating outside of the web browser.

CentOS

If using CentOS and the Software Collections, the trigger definitions above would be changed to look something like this:

saml-pre auth-pre-sso auth "scl enable python27 -- /path/pysaml/bin/python /path/saml_pre_sso.py %ssoArgs% %email%"
saml-sso auth-check-sso auth "scl enable python27 -- /path/pysaml/bin/python /path/saml_validate.py %ssoArgs% %email%"
saml-slo auth-invalidate auth "scl enable python27 -- /path/pysaml/bin/python /path/saml_logout.py %ssoArgs% %email%"

Use python27 for the 2.7 version, or rh-python36 for the 3.6 version.

Swarm Integration

When using Swarm with SAML integration enabled, the trigger invocation will need to be adjusted slightly. In the trigger table entry for auth-check-sso, add the option --no-id after saml_validate.py. This will disable the request identifier verification in the trigger, since the initial login request is generated in Swarm.

Additionally, the settings.json configuration in the sp section will need to have URLs that match what Swarm is advertising in its own config.php file. In particular, the sp.assertionConsumerService.url must match what Swarm has for the same field in the config.php, as well as the sp.entityId -- both must match exactly.

Details

The auth-pre-sso trigger saml_pre_sso.py is used to generate the SAML login URL. This is printed to standard output, and delivered to the desktop agent specified in the P4LOGINSSO environment setting on the client. The trigger can sign the request using private keys, if so configured.

The auth-check-sso trigger saml_validate.py receives the SAML response from the desktop agent and validates that it matches expectations. This includes verifying that the response is associated with the request initiated by saml_pre_sso.py, that the NameID matches the user value (either %email% or %user%, whichever was provided to the trigger), and that the response is otherwise a valid SAML response (e.g. time constraints, signature).

The auth-invalidate trigger saml_logout.py is invoked by p4d when the user invokes p4 logout, and is used to send a SAML logout request to the IdP. This happens "behind the scenes", and does not involve the desktop agent. The trigger generates the logout request using the NameID, session index, and browser cookies provided by the agent during the login process. The logout request can be signed, if the trigger is so configured.

Troubleshooting

The saml_logout.py trigger provides some basic logging, which is enabled with the --debug command line option. The trigger will write to ~/saml/logs directory by default. Each time the trigger runs it overwrites the previous log file, so this is really only useful for debugging.