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.
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.
libxml2
and xmlsec
librariesgcc
)ltdl
)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.
Depending on your operating system, there is distribution-specific preliminary steps, followed by the platform-independent Python module installation.
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
$ sudo apt-get install python2.7 python2.7-dev python-virtualenv
$ sudo apt-get install libxml2-dev libxmlsec1-dev libxmlsec1-openssl
$ 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.
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
$ sudo apt-get install python3 python3-dev python3-virtualenv
$ sudo apt-get install libxml2-dev libxmlsec1-dev libxmlsec1-openssl
$ 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.
The Python dependencies can be installed without the use of pip, if, for instance, your security policies require reviewing the software prior to installation.
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.
To start, download the compressed source for each of the required modules:
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
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.
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.
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.
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%"
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.
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
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
).
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.
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.
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.
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.
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.
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.
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.