README.md #4

  • //
  • guest/
  • perforce_software/
  • helix-saml/
  • main/
  • README.md
  • Markdown
  • View
  • Commits
  • Open Download .zip Download (13 KB)

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.

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.

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%"

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.

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.

SAML configuration

All of the various SAML details are configured in the settings.json and advanced_settings.json files. These two files should be in the same directory as the Python triggers. Most of the settings are self-explanatory, with additional information available on the python3-saml project page. The most important settings are those related to the definition of the "service provider", which must match that 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 instance, the Recipient will be the url value under assertionConsumerService in the settings.json file, and Audience will be the entityId value from that same file. The Relay State field, if any, can be left blank.

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.

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.

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.

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.

# SAML Triggers

The [SAML](https://wiki.oasis-open.org/security/FrontPage) 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.

## 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](https://www.python.org) and rely on the
[onelogin/python3-saml](https://github.com/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](https://pypi.org/project/pip/) utility, which can be installed like so:

```shell
$ 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](http://xmlsoft.org) and
[xmlsec](https://www.aleksey.com/xmlsec/). These are usually available as
packages on the popular Linux distributions. Examples are given below for
[Ubuntu](https://www.ubuntu.com) 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.

```shell
$ 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

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

#### Common

```shell
$ 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.

```shell
$ 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

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

#### Common

```shell
$ 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](https://pypi.org/project/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.

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:

```shell
$ 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%"
```

### Logout with Okta

If your IdP services are provided by [Okta](https://www.okta.com) 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.

### 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](https://www.json.org)
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.

```json
{
    "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](https://github.com/onelogin/python3-saml) project page.

### SAML configuration

All of the various SAML details are configured in the `settings.json` and
`advanced_settings.json` files. These two files should be in the same directory
as the Python triggers. Most of the settings are self-explanatory, with
additional information available on the
[python3-saml](https://github.com/onelogin/python3-saml) project page. The most
important settings are those related to the definition of the "service
provider", which must match that 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 instance, the **Recipient** will be the `url` value under
`assertionConsumerService` in the `settings.json` file, and **Audience** will be
the `entityId` value from that same file. The **Relay State** field, if any, can
be left blank.

### 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:

```shell
$ 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.

### 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.

#### 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.

## 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.
# Change User Description Committed
#9 26116 Nathan Fiedler Add note about EOL and new solution to trigger README.
#8 25615 Nathan Fiedler Streamline configuration instructions for SAML trigger.
#7 25097 Nathan Fiedler Add details for numeric ID and restarting p4d.
#6 25013 Nathan Fiedler Add basic Okta setup instructions.
#5 25000 Nathan Fiedler Update notes about security and triggers.
#4 24988 Nathan Fiedler Add proper logout support for Okta.

Use the Okta API to perform the logout, as that works much better
given our unusual scenario of operating outside of the web browser.

Added the requests library as a dependency as that permits us to
send a DELETE request with cookies, while the Python standard
library does not.
#3 24953 Nathan Fiedler Add note about Swarm, and flag to disable ID verification.

When using the SAML triggers with Swarm, it is necessary to add the
--no-id option to the saml_validate.py invocation, which disables
the request identifier verfication. This is because the current
release of Swarm generates the login URL, with a different request
identifier, and the trigger cannot validate the response.
#2 24789 Nathan Fiedler Add instructions for CentOS 6.
#1 24776 Nathan Fiedler Add the README file for the SAML triggers.