Setting up SAML Federation using the Python SDK

May 13, 2024 | 5 minute read
Vinay Kalra
Principal Cloud Solution Architect
Text Size 100%:

Introduction

Setting up Federation between two entities; a Service Provider (SP) and a Identity Provider(IdP) is a common use case.  There are advantages in setting federation as this post describes.  This use case happens so often that I decided to write a script to automate this process.
There are multiple ways to implement a script to setup federation; you can use the REST APIs, the OCI CLI or the Python SDK. I chose the Python SDK method over the OCI CLI as thiis provided more flexibility.  The Python SDK and REST APIs are essentially the same since the underlying techloogy are REST calls. In this post, I will be going over some of the details and lessons learned.  This post will not be covering basic knowledge of SAML 2.0 and concepts.  If you want to learn more check out this link .
 

Details

Before we begin here's a link to the code 'setupFed'. This is a GitHub Repo with a README file that describes the use case and usage. If you have set up federation before, you understand that there's a "dance"  between the Service Provider(SP) and the Identity Provider(IdP). For this reason the script runs within the context of an SP or IdP.  You must run the script as an SP first, which will create the required artifacts. Once completed successfully, the IdP administrator will need to run the same script within their tenancy.  Technically, you can run both steps in the same tenancy however, that defeats the purpose of federation; you can run both steps in the same tenancy during testing.  Ideally, you have two separate tenancies one acting as a SP and the second is acting as an IdP which is the authoritative source of user identities.  When configuring SAML/Federation, there is some communication between and SP admin and an IdP admin.  At a minimum you mustrs exchange metadata that describes the provider SAML configuration.  The script tries to automate as much as possible.  Running step 1 for SP you will need the IdP's metadata either in the form of a URL or file.
 

Service Provider Artifacts

Running the script with the '-sp' flag will create artifacts for a Serice Provider (SP).  OCI is the only provider supported for an SP.

Here is a list of artifacts that get generated for an SP:
  • Identity Provider Configuration - Using provided IdPs metadata URL or File
  • Confidential Application - Used with IdP's Generic SCIM Application
  • Bucket/Object Store - The IdP script (step 2) will consume the data stored in this bucket
  • A Pre-Authenticated Request (PAR) - Used when configuring the IdP.
 
You may notice that a bucket is created in this step.  I use this medium to store SP metadata required for the IdP.  The PAR is used to allow access to the SP data when you run the script as an IdP.
 

Identity Provider Artifacts

Running the script with the '-idp oci' flag will create artifacts for a Identity Provider (IdP).  Currently only OCI is supported for an IdP.  In the future, I hope to add other providers.

Here is a list of artifacts that get generated for an IdP in OCI:
  • Generic SCIM Application - Used to provision user/group data to SP
  • SAML Application - Configured to accept request from SP
 
When the Generic SCIM Application is created, we use the default values for user attributes to provision. These values usually suffice for most situations. However, you may need to modify these values in order to get your provisioning working.  This all depends on the structure of the user profile.
The SAML Application is needed so that the IdP will accept request from the SP; this is the "circle of trust" between the SP and IdP.
 
Access Permissions
The script can either be executed on a local laptop/desktop or within 'cloudshell' via the OCI console. In order to build these artifacts above the user must have the right permissions:
  • Full access to the identity domain in the compartment you specified in the script.
  • Access to create a bucket/object in the compartment you specified in the script.
 
The script will expect the OCI config and determine which user is accessing the system.  You can read about this here.
 
 
For 'cloudshell', the behavior is slightly different.  The user authentication method uses a delegated token.  Read the GitHub README file for details on how to run the script using the delegated token.
 
Lessons Learned
 
Lesson One
When creating the Generic SCIM Application for the IdP using the create_app() method, you will find that it is not complete. You also need to setup provisioning (SCIM) and you cannot do this using the create_app() method.  It is done after the application is created by using the patch_app() method as such:
 
# Use patch_app and pass JSON data to complete creation of app.
patch_app_response = domainClient.patch_app(
  app_id=myConfig[OCI_IDP]['app_id'] ,
  patch_op=oci.identity_domains.models.PatchOp(
  schemas=["urn:ietf:params:scim:api:messages:2.0:PatchOp"],
  operations=[
    oci.identity_domains.models.Operations(
      op="REPLACE",                                                                                     path="urn:ietf:params:scim:schemas:oracle:idcs:extension:managedapp:App:bun   dleConfigurationProperties[name eq \"host\"].value",
      value=[spData[SP]['host']]),
    oci.identity_domains.models.Operations(
       op="REPLACE",       path="urn:ietf:params:scim:schemas:oracle:idcs:extension:managedapp:App:bundleConfigurationProperties[name eq \"baseuri\"].value",
       value=[spData[SP]['baseuri']]),
    oci.identity_domains.models.Operations(
       op="REPLACE",     path="urn:ietf:params:scim:schemas:oracle:idcs:extension:managedapp:App:bundleConfigurationProperties[name eq \"clientid\"].value",
       value=[spData[SP]['client_id']]),
    oci.identity_domains.models.Operations(
       op="REPLACE",  path="urn:ietf:params:scim:schemas:oracle:idcs:extension:managedapp:App:bundleConfigurationProperties[name eq \"clientsecret\"].value",
       value=[spData[SP]['client_secret']]),
    oci.identity_domains.models.Operations(
       op="REPLACE",   path="urn:ietf:params:scim:schemas:oracle:idcs:extension:managedapp:App:bundleConfigurationProperties[name eq \"scope\"].value",
       value=[spData[SP]['scope']]),
    oci.identity_domains.models.Operations(
       op="REPLACE",      path="urn:ietf:params:scim:schemas:oracle:idcs:extension:managedapp:App:bundleConfigurationProperties[name eq \"authenticationServerUrl\"].value",
       value=[spData[SP]['asertion_consumer_url']])]))
As you can see, you must first create the application and thens use the 'app_id' in a subsequent call to patch_app().  This is the recommened approach.
 
Lesson Two
I wanted to make the script re-entrant, meaning if you have created some artifacts and wanted to run the script again (i.e testing) it would still run without errors.  In this case, if the artifacts exists you will get an HTTP Status Code 409: Duplicate warnings.  These exceptions needed to be handled. 
 
I handled the HTTP Status Code 409 in two ways:
  1. Do nothing and continued and
  2. Gather info about existing application using get_app().
 
In certain cases, I needed some values from the existing application which is not available during a HTTP 409 exception. Therefor I stored the existing application ID from a previous call and used the get_app() API call to get values I needed.
 
Thanks for reading!
 
 

Vinay Kalra

Principal Cloud Solution Architect

Vinay Kalra is a 'Principal Cloud Solution Architect' of the North America Cloud Technology and Engineering Team. Vinay's area of expertise is in Identity and Access Management (IAM) and Security for Oracle Cloud Infrastrucure (OCI).  Vinay has been in this space as a Software Engineer/Architect for over 20 years,


Previous Post

User-based Access on OCI using OpenVPN

Aditya Kulkarni | 8 min read

Next Post


CISO Perspectives: PCI DSS 4.0

Leia Manchanda | 8 min read