Below you will find information about the SAML2 implementation. A short description of what is involved is described here. Put simply:
- Identity Provider: An identity provider, or
idp
, is ESA's authoritative service for performing authentication on behalf of other services (it enables the SSO function). - Service Provider: A service provider, or
sp
, is any service that wants to be involved in the federated identity network. It performs a service for users, like offering a portal to manage Activites on.
In SAML2, both identity provider and service-provider need to configure themselves to know about one another
idp.xml
: An XML file that describes the identity provider service. It's usually available at a public URL for services to read.sp.xml
: In this case,sp.xml
refers to our service provider. This document must be given to theidp
so that they can authorize your service to use their identity provider. It can be given by email, but is often also hosted somewhere on the service provider itself (typically on a URL that doubles as the EntityID (unique identifier) of the service). This is so that when (and if) encryption keys expire, an identity provider can just pull the latest embedded certificates from the publically availablesp.xml
path.
Finally, a few more useful keywords:
EntityID
: This is just a unique string. It could be "banana". It just has to be unique. Normally, the website URL is used, and oftentimes the path to thesp.xml
metadata file is used so that it can double in purpose as a url for fetching the latestsp.xml
if needed.Assertion Customer URL
: Also called "Assertion customer service" oracs
, this is just an endpoint that handles the parsing of replies from theidp
to thesp
. In other words, it's just a handle on your server for listening in and processing SAML2 protocol messages. It's handled by the library so you just need to route incoming traffic to it.
Business Justification
This request is necessary to enable the ESA Activities Portal developer server to behave as a service provider within the ESA's Azure Enterprise Appication Federation Service. It shall allow staff to internally evaluate and perform individual actions within the service.
Mandatory Documents
Below you'll find our service-provider (sp.xml
) configuration, which must be given to IT (attach it as an XML file).
<?xml version="1.0"?>
<md:EntityDescriptor xmlns:md="urn:oasis:names:tc:SAML:2.0:metadata"
validUntil="2024-12-03T23:31:37Z"
cacheDuration="PT604800S"
entityID="http://django-activities-demo.go.esa.int:8000/esa-sso/metadata/">
<md:SPSSODescriptor AuthnRequestsSigned="false" WantAssertionsSigned="false" protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol">
<md:SingleLogoutService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect"
Location="http://django-activities-demo.go.esa.int:8000/logout/" />
<md:NameIDFormat>urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified</md:NameIDFormat>
<md:AssertionConsumerService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"
Location="http://django-activities-demo.go.esa.int:8000/esa-sso/acs/"
index="1" />
</md:SPSSODescriptor>
</md:EntityDescriptor>
You can generate this XML anytime using an online tool like samltool. Here is what you need to know about the fields. Feel free to adjust some of the metadata fields, but note that the SAML2 specific authentication fields and certificate details should be left as-is:
Field | Format | Example |
---|---|---|
EntityID | <domain-name>/esa-sso/metadata/ |
http://django-activities-demo.go.esa.int:8000/esa-sso/metadata/ |
ACS | <domain-name>/esa-sso/acs/ |
http://django-activities-demo.go.esa.int:8000/esa-sso/acs/ |
Logout | <domain-name>/logout/ |
http://django-activities-demo.go.esa.int:8000/logout/ |
NameID | Leave default |
Leave default |
SP X.509 Cert | None |
None |
AuthnRequestsSigned | None |
None |
WantAssertionsSigned | None |
None |
Organization Name | string |
ESA |
Organization DisplayName | string |
Activities Portal |
Organization URL | string |
None |
Technical Contact Name | None |
None |
Support Contact Name | None |
None |
Technical Contact Email | None |
None |
Support Contact Email | None |
None |
Private key to sign metadata | None |
None |
X.509 cert to sign metadata | None |
None |
Application Identifier
Termed here the EntityID:
http://django-activities-demo.go.esa.int:8000/esa-sso/acs/
Assertion Custom URL (SAML) / Callback URL (Open ID)
Do not forget the trailing slash:
http://django-activities-demo.go.esa.int:8000/esa-sso/acs/
Logout URL
Do not forget the trailing slash:
http://django-activities-demo.go.esa.int:8000/logout/
Secure hash algorithm
SHA-256
Staff Application Coordinator
NA
Environment
Development
Claims Details
email, first_name, last_name,
Preferred authentication profile:
None
In order for any identity provider to work with a service provider, they need to exchange configuration files:
sp.xml
: Provided to the identity provider. Tells it about the serviceidp.xml
: Provided to the service provider. Tells it about the identity provider.
--
This SAML2 implementation is done using django-saml2-auth, which requires the host have xmlsec1
library installed. Use:
apt-get install xmlsec1
for DEB-based distributions, or find an equivalent for your platform.
Add the library to the list of installed apps:
# activities/activities/settings.py
INSTALLED_APPS = [
...
'django_saml2_auth',
]
Provide the SAML2 configuration.
# activities/activities/settings.py
SAML2_AUTH = {
# URL to the ESA identity provider
'METADATA_AUTO_CONF_URL': 'http://localhost:8088/idp.xml',
# Unique identifier for this service (need not be a valid url).
'ENTITY_ID': 'http://localhost:8000/esa-sso/metadata/',
# Automatic mapping from identity provider assertion to local user model fields
'ATTRIBUTES_MAP': {
'username': 'uid',
},
# Security settings.
'AUTHN_REQUESTS_SIGNED': False,
'WANT_ASSERTIONS_SIGNED': False,
'WANT_RESPONSE_SIGNED': False,
'TOKEN_REQUIRED': False,
# Debugger settings
'DEBUG': True,
}
Add your single-sign-on SSO library path hooks next:
# activities/activities/urls.py
from django.urls import path, include
from django_saml2_auth.views import signin
urlpatterns = [
...,
# Built-in paths by the library
path("esa-sso/", include("django_saml2_auth.urls")),
# The local URL you send the user to that will redirect them to the identity-provider login
path("esa/login", signin, name="esa-sso-login"),
# The URL where you will host your metadata file, to allow automatic fetching by IDP
# (happens to be the same as the Entity-ID for convenience)
path("esa-sso/metadata", MetadataView)
]
Add the link to the SSO signin on the login page:
<h1>Login Providers</h1>
<div class="login-container">
<div class="login-option">
<a href="{% url 'esa-sso-login' %}">ESA</a>. # <-- New
<p>For staff</p>
</div>
<div class="login-option">
<a href="/osip/login">OSIP</a>
<p>For external parties</p>
</div>
</div>
Create a test identity provider. In order for any identity provider to work with a service provider, they need to exchange configuration files:
sp.xml
: Provided to the identity provider. Tells it about the serviceidp.xml
: Provided to the service provider. Tells it about the identity provider.
In our case, our identity provider will just be a simple server that serves a generated idp.xml
on a web address. This address is exactly the same that should be configured in the SAML2 METADATA_AUTO_CONF_URL
field.
# Make directory for our identity provider service.
cd activities/website/
mkdir idp && cd idp
# Fetch an example public/private key for the idp. Suitable for testing
mkdir pki
curl -o pki/mycert.pem https://raw.githubusercontent.com/IdentityPython/pysaml2/master/example/idp2/pki/mycert.pem
curl -o pki/mykey.pem https://raw.githubusercontent.com/IdentityPython/pysaml2/master/example/idp2/pki/mykey.pem
# Fetch configuration files to generate our sp.xml metadata file
curl -o service_conf.py https://raw.githubusercontent.com/IdentityPython/pysaml2/master/example/sp-wsgi/service_conf.py.example
curl -o sp_conf.py https://raw.githubusercontent.com/IdentityPython/pysaml2/master/example/sp-wsgi/sp_conf.py.example
# Use stream editor to replace some fields. Feel free to also open "sp_conf.py" and add adjustments manually
sed -e 's|../idp2/idp.xml|idp.xml|' \
-e 's|8087|8000|' \ # <-- Port of our service provider
-e 's|acs/post|esa-sso/acs/|' \ # <-- Path to 'acs' in our django_saml2_auth.urls path.
-e 's|slo/redirect|logout/|' \ # <-- Where we want logout to go.
-e 's|slo/post|logout/|' \ # <-- Where we want logout to go.
-e 's|%ssp.xml" % (BASE, ""),|esa-sso/metadata/" % BASE,|' \. # <-- Entity ID of our service provider
sp_conf.py > sp_conf.tmp && mv sp_conf.tmp sp_conf.py
# Generate the service provide metadata
curl -o idp.xml https://raw.githubusercontent.com/IdentityPython/pysaml2/master/example/idp2/idp.xml
make_metadata sp_conf.py > sp.xml
# Download a configuration script for setting up the IDP server:
curl -o idp_conf.py https://raw.githubusercontent.com/IdentityPython/pysaml2/master/example/idp2/idp_conf.py.example
curl -o idp.py https://raw.githubusercontent.com/IdentityPython/pysaml2/master/example/idp2/idp.py
curl -o idp_user.py https://raw.githubusercontent.com/IdentityPython/pysaml2/master/example/idp2/idp_user.py
# Adjust the IDP server settings to read our service-provider XML file and to not require HTTPS
sed -e 's|../sp-wsgi/sp.xml|sp.xml|' \
-e 's|HTTPS = True|HTTPS = False|' \
idp_conf.py > idp_conf.tmp && mv idp_conf.tmp idp_conf.py
# Add some templates for the identity provider to serve as a login form
mkdir htdocs
curl -o htdocs/root.mako https://raw.githubusercontent.com/IdentityPython/pysaml2/master/example/idp2/templates/root.mako
curl -o htdocs/login.mako https://raw.githubusercontent.com/IdentityPython/pysaml2/master/example/idp2/htdocs/login.mako
# Add the dependencies to use them
python3 -m pip install cherrypy mako legacy-cgi
# Generate the idp.xml
make_metadata idp_conf.py > idp.xml
You may run the server with: python idp.py idp_conf