Copyright 2015-2021, Tremolo Security, Inc.

Introduction

OpenUnison combines the identity services that are most used by applications into a single system that is quick to deploy, easy to use, and simple to maintain. The identity services provided by OpenUnison include:

  • Authentication - Who are you? How do we know that?

  • Session Management - How did you login? What do you have access to?

  • Identity Federation - SSO across domains

  • User Provisioning - Creating and managing identity data in systems and applications

  • Access Request Management - Why do you have access?

OpenUnison itself is a J2EE application that can be deployed quickly and easily. OpenUnison has been tested with Tomcat 8.x and with Undertow, but should run in any servlet 3.x container.

Web Access Manager

When deployed for web access management, OpenUnison acts as a reverse proxy, sitting between your users and your web applications.

OpenUnison Reverse Proxy

When a user attempts to access a protected web application, his/her session is proxied through OpenUnision per the following:

  1. User makes a request the application (by entering a URL, clicking a link, accessing a bookmark, etc.)

  2. OpenUnison receives the request and checks to see if the URL is protected

  3. If the URL is protected, OpenUnison determines whether the user is authenticated

    1. If the user is authenticated, OpenUnison determines whether the authentication level is valid

    2. If the user isn’t authenticated, or the authentication level isn’t valid, OpenUnison executes the appropriate authentication chain

  4. OpenUnison checks determines whether the user is authorized

  5. If the user is authorized, OpenUnison may execute a series of filters to manipulate the request or response

  6. OpenUnison generates a Last Mile token that contains the user’s attributes, nonce, time range and the request URI

  7. The request is sent to the application

  8. The application, deployed with a Last Mile component, validates the Last Mile token and sets the user’s context

The reverse proxy model offers several advantages over a typical agent model:

  1. Performance - In a proxy model there are no agents that need to "phone home" to a centralized server to verify access.

  2. Ease of development - Developers can write applications "in a vacuum" and integrate with the identity management system later.

  3. Ease of deployment - The Last Mile components used by OpenUnision are lighter than typical agents and do not require separate processes which often cause issues with web servers.

  4. Just-In-Time Provisioning - Application user accounts can be created on the fly when they are needed - just before user login.

User Provisioning System

In addition to web access management, OpenUnison provides a user provisioning system. The APIs comprising OpenUnison’s provisioning system can be used directly by applications or in conjunction with Tremolo Security ScaleJS to provide an interface for user registration, resource access requests and access approvals via workflows. The provisioning system can even be used to build a private Identity as a Service (IDaaS) solution. Provisioning data is recorded in a flexible audit database that allows customized reporting on all details from access request to approval and provisioning.

ScaleJS

ScaleJS is an AngularJS application that uses RESTful web services to interact with OpenUnison. The application can be downloaded from https://www.tremolosecurity.com/downloads or directly from the OpenUnison github repo. The services may be used on their own without ScaleJS. Details for each service is in the integration guide. The configuration of each application is included in the Filter reference of this guide.

ScaleJS and OpenUnison Architecture

LDAP Virtual Directory

OpenUnison integrates MyVirtualDirectory as an internal LDAP virtual directory. Integrating MyVD makes it easier for OpenUnison to connect to almost any data source such as directories, relational databases and web services. The provisioning services provide a limited search capability, but there’s no LDAP interface provided. The configurations of the embedded MyVirtualDirectory can be used in a stand alone MyVirtualDirectory server.

Deploying OpenUnison on Undertow

The easiest way to deploy OpenUnison is using the embedded Undertow (http://undertow.io) web server. OpenUnison can be built to have Undertow directly integrated, making patching and updating very simple. Using this process also fits in well with both a continuous integration process as well as legacy build/test/deploy cycles. Deploying OpenUnison on Undertow uses the below workflow:

OpenUnison build diagram

Maven is used with the overlay plugin to combine your specific configurations with the standard OpenUnison build to create a war file that includes:

  1. OpenUnison’s baseline dependencies

  2. Undertow

  3. Your configuration and libraries

The easiest way to get started is to fork one of Tremolo Security’s "Quick Start" projects from github. To get started, we’re going to use the S2I OpenUnison quick start.

$ git clone https://github.com/TremoloSecurity/openunison-qs-simple.git
Cloning into 'openunison-qs-simple'...
remote: Counting objects: 17, done.
remote: Compressing objects: 100% (14/14), done.
remote: Total 17 (delta 1), reused 13 (delta 0), pack-reused 0
Unpacking objects: 100% (17/17), done.

Once checked out, we can build OpenUnison

$ mvn package

Once the build is completed, target/openunison-qs-simple-1.0.war will be available for deployment. The next step is to setup the OpenUnison deployment. Assuming you want to deploy into /opt/openunison create the following directories:

Directory

Description

/opt/openunsion/work

Stores temporary files

/opt/openunison/war

Location for the war file

/opt/openunison/config

Configuration files

/opt/openunison/quartz

Generated scheduler configuration

/opt/openunison/amq

area for embedded ActiveMQ server

/opt/openunison/bin

Scripts for starting and stopping OpenUnison

The next step os tp go into /opt/openunison/config to create the keystore and paramters file:

Create Keystore TLS key
$ keytool -genkeypair -storetype PKCS12 -alias unison-tls -keyalg RSA -keysize 2048 -sigalg SHA256withRSA -keystore ./unisonKeyStore.p12 -validity 3650
Enter keystore password:
Re-enter new password:
What is your first and last name?
  [Unknown]:  localhost.localdomain
What is the name of your organizational unit?
  [Unknown]:  demo
What is the name of your organization?
  [Unknown]:  demo
What is the name of your City or Locality?
  [Unknown]:  demo
What is the name of your State or Province?
  [Unknown]:  demo
What is the two-letter country code for this unit?
  [Unknown]:  demo
Is CN=localhost.localdomain, OU=demo, O=demo, L=demo, ST=demo, C=demo correct?
  [no]:  yes

Enter key password for <unison-tls>
        (RETURN if same as keystore password):
Note
Make sure that your keystore password and key password are the same.

Next, create a sessoin key

Create OpenUnison Session Key
$ keytool -genseckey -alias session-unison -keyalg AES -keysize 256 -storetype PKCS12 -keystore ./unisonKeyStore.p12
Note
Make sure that your keystore password and key password are the same.

Once the keystore is created, create a file called ou.env with the bellow content:

ou.env File
OU_HOST=localhost.localdomain
TEST_USER_NAME=testuser
TEST_USER_PASSWORD=secret_password
unisonKeystorePassword=start123
unisonKeystorePath=/opt/openunison/config/unisonKeyStore.p12
Note
make sure you specify the passwords you used earlier to create the keystore.

This file will be used to store values that are specific to your deployment and environment. As an example the OU_HOST would be different in your development environment instead of your production environment but the policies, applications, etc don’t change.

Next create a file called run_openunison_config.conf:

run_openunison_config.conf
PATH_TO_RUN=/opt/openunison/bin
OPENUNISON_WAR=/opt/openunison/war/openunison.war
OPENUNISON_YAML=/opt/openunison/config/openunison.yaml
OPENUNISON_DEPLOYMENT_PATH=/opt/openunison/work
OPENUNISON_QUARTZ_PATH=/opt/openunison/quartz

This file has some environment variables that tells the start and stop scripts where to find OpenUnison. Next create the /opt/openunison/config/openunison.yaml file:

openunison.yaml
---
open_port: 8080
open_external_port: 80
secure_port: 8443
secure_external_port: 443
disable_http2: false
allow_un_escaped_chars: false
context_root: "/"
secure_key_alias: "unison-tls"
force_to_secure: true
activemq_dir: "/opt/openunison/amq"
quartz_dir: "/opt/openunison/quartz"
client_auth: none
allowed_client_names: []
ciphers:
- TLS_RSA_WITH_RC4_128_SHA
- TLS_RSA_WITH_AES_128_CBC_SHA
- TLS_RSA_WITH_AES_256_CBC_SHA
- TLS_RSA_WITH_3DES_EDE_CBC_SHA
- TLS_RSA_WITH_AES_128_CBC_SHA256
- TLS_RSA_WITH_AES_256_CBC_SHA256
path_to_deployment: "/opt/openunison/work"
path_to_env_file: "/opt/openunison/config/ou.env"
welcome_pages: ['index.html']
redirect_to_context_root: false

Now that configuration is complete, pull down the script files from GitHub into /opt/openunison/bin:

$ wget https://raw.githubusercontent.com/TremoloSecurity/OpenUnison/1.0.17/unison/openunison-on-undertow/src/main/bash/runOpenUnisonOnUndertow.sh
$ wget https://raw.githubusercontent.com/TremoloSecurity/OpenUnison/1.0.17/unison/openunison-on-undertow/src/main/bash/startOpenUnisonOnUndertow.sh
$ wget https://raw.githubusercontent.com/TremoloSecurity/OpenUnison/1.0.17/unison/openunison-on-undertow/src/main/bash/stopOpenUnisonOnUndertow.sh

Last, copy openunison-qs-s2i/target/openunison-qs-simple-1.0.war to /opt/openunison/war/openunison.war.

Starting OpenUnison

Assuming OpenUnison is installed in /opt/openunison:

$ cd /opt/openunison/bin
$ ./startOpenUnisonOnUndertow.sh ../config/run_openunison_config.conf

Stopping OpenUnison

Assuming OpenUnison is installed in /opt/openunison:

$ cd /opt/openunison/bin
$ ./stopOpenUnisonOnUndertow.sh ../config/run_openunison_config.conf

Patching OpenUnison

Everything is contained in the war file, even the Undertow classes. Patching is a 4 step process:

  1. Update version numbers in the pom.xml

  2. Build the project using mvn package

  3. Copy the resulting war to /opt/openunison/war/openunison.war

  4. Restart OpenUnison

Security

A security strategy for OpenUnison should encompass how OpenUnison is configured, permissions for accessing OpenUnison and secret management.

Configuring OpenUnison

Its recommended that OpenUnison ALWAYS run with TLS enabled and redirect all requests to TLS. Additionally, call cookies should be marked as "secure" and "httponly". Finally, each application session cookie is encrypted. If SSO should not exist between applications, make sure that different encryption keys are used. This will prevent attackers from manipulating cookies to gain access to applications they should have to reauthenticate to.

File System Permissions

OpenUnison is built on Java and should be run as an unprivileged user. If access to ports below 1024 is required (ie 80 and 443) then iptables or firewalld should be used to forward requests from those ports to the ports OpenUnison is configured to listen on.

Secret Management

OpenUnison can merge in configuration options from an outside file. This process allows for secrets to be stored outside of OpenUnison’s configuration. Since OpenUnison is configured using text files in a Maven project, these files are often stored in source control systems like Git so its very important NOT to store secrets in them.

Configuring OpenUnison

Reverse Proxy

OpenUnison’s reverse proxy capability is configured around applications and URLs. An application is a logical collection of URLs. Each URL is made up of a sequence of hosts, a collection of filters and potential authorization rules.

Before configuring an application you should know:

  1. Whats the URL of the application going to be?

  2. What host and port will the application be sitting behind the reverse proxy?

  3. Do you want SSO with other applications?

  4. How will your users authenticate?

  5. How will OpenUnison tell your application who’s logged in?

Once you have the answers to these questions you can begin configuring OpenUnison.

Authentication Mechanisms (authMechs)

Authentication Mechanisms define the ways in which a user can be authenticated. Prior to being added to an authentication chain, a mechanism must be defined in the section. Every authentication method has its own configuration parameters. See the Authentication Mechanisms section of the Configuration Reference for configuration options on specific mechanisms. Here’s an example of the form login mechanism:

<!--The name attribute is what is referenced in the authentication chain -->
<mechanism name="loginForm">
 <!-- The URI is where the user is sent when its time to authenticate with this mechanism, MUST start with /auth -->
 <uri>/auth/formLogin</uri>

 <!-- The class name of the authentication mechanism -->
 <className>com.tremolosecurity.proxy.auth.FormLoginAuthMech</className>

 <!-- List of optional initialization parameters for the mechanism -->
 <init>
 </init>

 <!-- Optional list of the parameters that are configured on the chain -->
 <params>
  <param>FORMLOGIN_JSP</param>
 </params>
</mechanism>

Once a mechanism is configured, its available to an authentication chain.

Authentication Chains (authChains)

Mechanisms are tied together in chains that allow for multiple mechanisms to be executed when a user attempts to authenticate. This way, an application can require multiple mechanisms for authentication. For instance, the user may first login with a username and password, but then be required to accept terms and conditions. A chain can also be used for multi-factor authentication. Once mechanisms are available, they can be added to a chain.

Compliance

In order to comply with most security requirements systems must be protected from brute force attacks where a user can repeatedly try to authenticate with passwords until one is found. OpenUnison can help protect against this by adding a compliance section to an authentication chain. This will trigger OpenUnison to track the last time a user failed to log in, succeeded and how many failures have occurred. In order to use this feature the following attributes must be available in the virtual directory:

Description

Data Type

Description

Last Successful Authentication

Directory String OR Long Integer

Stores the last successful authentication in the form of the number of milliseconds since epoch

Last Failed Authentication

Directory String OR Long Integer

Stores the last failed authentication attempt in the form of the number of milliseconds since epoch

Number of failed authentications

Directory String or Integer

The number of failed attempts since the last success

In addition to these attributes, a workflow will need to be created that can update these attributes as needed. An example:

<workflow name="updateLockout">
        <tasks>
                <ifAttrExists name="unisonFailedLogins">
                        <onFailure>
                                <customTask className="com.tremolosecurity.provisioning.customTasks.LoadAttributes">
                          <param name="name" value="unisonFailedLogins"/>
                          <param name="nameAttr" value="uid"/>
                        </customTask>
                        </onFailure>
                </ifAttrExists>
                <ifAttrExists name="unisonLastFailedLogin">
                        <onFailure>
                                <customTask className="com.tremolosecurity.provisioning.customTasks.LoadAttributes">
                          <param name="name" value="unisonLastFailedLogin"/>
                          <param name="nameAttr" value="uid"/>
                        </customTask>
                        </onFailure>
                </ifAttrExists>
                <ifAttrExists name="unisonLastSuccessLogin">
                        <onFailure>
                                <customTask className="com.tremolosecurity.provisioning.customTasks.LoadAttributes">
                          <param name="name" value="unisonLastSuccessLogin"/>
                          <param name="nameAttr" value="uid"/>
                        </customTask>
                        </onFailure>
                </ifAttrExists>
                <provision sync="true" target="activedirectory"/>
        </tasks>
</workflow>

The below XML shows an example chain:

<!-- The name is how this chain is referenced in the URL -->
<!-- The level defines how strong an authenticaiton should be -->
<!-- The root defines where in the virtual directory OpenUnison should look for users. If not set, OpenUnison will look in o=Tremolo -->
<chain name="formlogin" level="20" root="o=Tremolo">
  <!-- Optional element to enable compliance for authentication
          enabled - Determines if compliance should be enabled for this chain
          maxFailedAttempts - The number of allowed attempts until a user is considered locked out
          maxLockoutTime - The number of milliseconds a user is locked out before they can log back in
          numFailedAttribute - The name of the attribute that stores the number of failed attempts
          lastFailedAttribute - The name of the attribute that stores the last failed login attempt Timestamp
          lastSucceedAttribute - The name of the attribute that stores the last successful login attempt timestamp
          updateAttributesWorkflow - The name of the workflow that will update attributes on login attempts
          uidAttributeName - The name of the attribute in the provisioning target used to identify this user, MUST be on the authenticated user's object in the virtual directory -->

  <compliance enabled="true"
              maxFailedAttempts="5"
              maxLockoutTime="1200000"
              numFailedAttribute="unisonFailedLogins"
              lastFailedAttribute="unisonLastFailedLogin"
              lastSucceedAttribute="unisonLastSuccessLogin"
              updateAttributesWorkflow="updateLockout"
              uidAttributeName="uid" />

 <!-- Each mechanism should be listed in the order that they're to be executed in -->
 <authMech>
  <!-- The name attribute of the mechanism -->
  <name>loginForm</name>

  <!-- Determines how the mechanism is required.  If "required" then the chain can not succeed without this mechanism succeeding.  If "optional" then the chain can succeed if this mechanism fails -->
  <required>required</required>

  <!-- List of optional parameters for the mechanism while executing the chain.  For specific mechanisms, see the Authentication Mechanisms section of  -->
  <params>
   <param name="FORMLOGIN_JSP" value="/auth/forms/defaultForm.jsp"/>
  </params>
 </authMech>
</chain>

Authentication Levels

Each chain has an authentication level. This level is used to determine how "strong" the authentication is. If a URL is configured with a chain of a certain level, OpenUnison will respond in one of two ways:

  1. If the level of the chain on the URL is HIGHER then the level the user is currently authenticated to then the user will be forced to authenticate with the chain configured on the URL

  2. If the level of the chain on the URL is EQUAL to or LESS then the level the user is currently authenticated to then the user will NOT be forced to re-authenticate

This allows you to mix and match authentication types depending on the user base. For instance US Federal Government workers have PIV cards, but private industry might have a TOTP credential. By using authentication levels you can ensure they have access to the same resources. A best practice is to assign levels in alignment with NIST 800-63 but in 10s instead of 1s:

  • 0 - Anonymous

  • 10 - No level of assurance

  • 20 - Some level of assurance

  • 30 - Medium level of assurance

  • 40 - High level of assurance

This way you can differentiate between different types of authentication in the same category providing some additional room for customization.

Result Groups (resultGroups)

A result group is a collection of results that could occur because of an event. There are four types of events -

  • Authentication Success

  • Authentication Failure

  • Authorization Success

  • Authorization Failure

A result can be one of three actions:

  • Create a header

  • Create a cookie

  • Send a redirect

Finally, a result’s value may come from one of three sources:

  • A user attribute

  • A static value

  • A custom class (com.tremolosecurity.proxy.results.CustomResult)

The below is an example of a result group configuration:

<!-- The name attribute is how the resultGroup is referenced in the URL -->
<resultGroup name="OnAzSuccess">
 <!-- Each result should be listed -->
 <result>
  <!-- The type of result, one of cookie, header or redirect -->
  <type>cookie</type>

  <!-- The source of the result value, one of user, static, custom -->
  <source>static</source>

  <!-- Name of the resuler (in this case a cookie) and the value -->
  <value>urlSuccessCookie=true</value>
 </result>
 <result>
  <type>header</type>
  <source>user</source>
  <value>result-uid=uid</value>
 </result>
 <result>
  <type>cookie</type>
  <source>user</source>
  <value>result-cookie=uid</value>
 </result>
 <result>
  <type>header</type>
  <source>custom</source>
  <value>mycustomheader=com.company.className</value>
 </result>
</resultGroup>

Applications (applications)

Once your authentication mechanisms, chains and result groups are configured, you can configure your application. Each application is broken into:

  1. URLs

  2. Cookie Configuration

Each URL comprises the components you’d expect from a URL such as host, path and transformations. The cookie configuration defines how an application’s session is managed. Below is an example application configuration:

<application name="AzCheck" azTimeoutMillis="30000" hsts="true" hstsTTL="31536000">
  <urls>
   <!-- The regex attribute defines if the proxyTo tag should be interpreted with a regex or not -->
   <!-- The authChain attribute should be the name of an authChain -->
    <url regex="false" authChain="formlogin">
      <!-- Any number of host tags may be specified to allow for an application to work on multiple hosts.  Additionally an asterick (*) can be specified to make this URL available for ALL hosts -->
      <host>localhost.localdomain</host>

      <!-- The filterChain allows for transformations of the request such as manipulating attributes and injecting headers -->
      <filterChain>

       <!-- The last mile filter the most secure mechanism OpenUnison has to integrate with a down stream application.  See the Configuration Reference for all of the filters available with OpenUnison.  NOTE: any param listed multiple times will be interpreted as a multi-value attribute -->
       <filter class="com.tremolosecurity.proxy.filters.LastMile">
        <param name="sigKeyAlias" value="lastmile"/>
        <param name="encKeyAlias" value="lastmile"/>
        <param name="sigKeyPass" value="start123"/>
        <param name="encKeyPass" value="start123"/>
        <param name="timeScew" value="60"/>
        <param name="headerName" value="autoidmrequest" />
        <param name="attribs" value="uid=from-assertion-uid"/>
        <param name="attribs" value="sn=from-assertion-sn"/>
        <param name="attribs" value="cn=from-assertion-cn"/>
       </filter>
      </filterChain>

      <!-- The URI (aka path) of this URL -->
      <uri>/echo2/echo</uri>

      <!-- Tells OpenUnison how to reach the downstream application.  The ${} lets you set any request variable into the URI, but most of the time ${fullURI} is sufficient -->
      <proxyTo>http://ubuntu14.tremolo.lan${fullURI}</proxyTo>

      <!-- List the various results that should happen -->
      <results>
       <azSuccess>AzSuccessHeader</azSuccess>



      </results>

      <!-- Determine if the currently logged in user may access the resource.  If ANY rule succeeds, the authorization succeeds.
          The scope may be one of group, dn, filter, dynamicGroup or custom
          The constraint identifies what needs to be satisfied for the authorization to pass and is dependent on the scope:
            * group - The DN of the group in OpenUnison's virtual directory (must be an instance of groupOfUniqueNames)
            * dn - The base DN of the user or users in OpenUnison's virtual directory
            * dynamicGroup - The DN of the dynamic group in OpenUnison's virtual directory (must be an instance of groupOfUrls)
            * custom - An implementation of com.tremolosecurity.proxy.az.CustomAuthorization -->
      <azRules>
       <rule scope="group" constraint="cn=staticgroup,ou=internal,ou=GenericLDAP,o=Tremolo" />
      </azRules>

    </url>


  </urls>

  <!-- The cookie configuration determines how sessions are managed for this application -->
   <cookieConfig>
   <!-- The name of the session cookie for this application.  Applications that want SSO between them should have the same cookie name -->
   <sessionCookieName>autoIdmSession</sessionCookieName>

   <!-- The domain of component of the cookie -->
   <domain>localhost.localdomain</domain>

   <!-- The URL that OpenUnison will interpret as the URL to end the session -->
   <logoutURI>/logout</logoutURI>

   <!-- The name of the AES-256 key in the keystore to use to encrypt this session -->
   <keyAlias>sesion-unison</keyAlias>

   <!-- If set to true, the cookie's secure flag is set to true and the browser will only send this cookie over https connections -->
   <secure>false</secure>

   <!-- If set to true, the cookie will be HttpOnly -->
   <httpOnly>true</httpOnly>

   <!-- The number of secconds that the session should be allowed to be idle before no longer being valid -->
    <timeout>900</timeout>

    <!-- determines the SameSite attribute of a cookie -->
    <!-- options are None, Lax, Strict, Ignore -->
    <sameSite>None</sameSite>

    <!-- session management can be disabled for APIs that do not require it -->
    <cookiesEnabled>true</cookiesEnabled>
  </cookieConfig>
</application>

Identity Providers

OpenUnison can act as an identity provider in addition to a reverse proxy. When an application is an identity provider, it has some minor differences with the reverse proxy:

  1. The application can have only one URL that starts with /auth/idp/

  2. URLs have additional configuration points:

    1. Attribute mappings

    2. Trusts

Attribute mappings are used to determine which (if any) attributes will be included in the assertion. Trusts are used to establish the connection between the identity provider and the service provider / relying party.

If you plan on using another SAML2 or OpenID Connect provider for authentication, use a LoginService as your authentication chain. If only one option is configured in the service it will NOT prompt the user to choose.

Below is an example configuration:

<!-- Setting isApp to false indicates to OpenUnison this is an identity provider, not a proxied application -->
<application name="MyIdP" isApp="false">
            <!-- on a single URL is allowed on an identity provider -->
            <urls>
             <!-- The regex and authChain attributes are ignored -->
                <url regex="false">
                 <!-- Any number of host tags may be specified to allow for an application to work on multiple hosts.  Additionally an asterick (*) can be specified to make this URL available for ALL hosts -->
                    <host>unison.enterprise.com</host>

                    <!-- The filterChain on an IdP is typically used to add attributes to the user prior to mapping into the assertion -->
                    <filterChain/>

                    <!-- The URI MUST start with /auth/idp/ -->
                    <uri>/auth/idp/MyIdP</uri>

                    <!-- List the various results that should happen -->
                    <results>
                        <auSuccess></auSuccess>
                        <auFail>Default Login Failure</auFail>
                        <azSuccess></azSuccess>
                        <azFail>Default Login Failure</azFail>
                    </results>

                    <!-- Determine if the currently logged in user may access the idp.  If ANY rule succeeds, the authorization succeeds.
              The scope may be one of group, dn, filter, dynamicGroup or custom
              The constraint identifies what needs to be satisfied for the authorization to pass and is dependent on the scope:
                * group - The DN of the group in OpenUnison's virtual directory (must be an instance of groupOfUniqueNames)
                * dn - The base DN of the user or users in OpenUnison's virtual directory
                * dynamicGroup - The DN of the dynamic group in OpenUnison's virtual directory (must be an instance of groupOfUrls)
                * custom - An implementation of com.tremolosecurity.proxy.az.CustomAuthorization -->
                    <azRules>
                        <rule scope="filter" constraint="(objectClass=*)"/>
                    </azRules>

                    <!-- Defines the IdP specific portions of the application -->
                    <idp className="com.tremolosecurity.idp.providers.Saml2Idp">
                     <!-- The alias of the certificate key used to decrypt any inbound encrypted requests -->
                        <params name="encKey" value=""/>

                        <!-- The alias of the certificate to to sign all outbound requests -->
                        <params name="sigKey" value="sigenc-idp-saml2-sig"/>

                        <!-- If set to true, requires all authnRequests to be signed -->
                        <params name="requireSignedAuthn" value=""/>


                        <!-- Determines which attributes to include in the assertion. -->
                        <!-- strict - If true then ONLY attributes specificly named in the mapping will be added -->
                        <mappings strict="true">
                         <!-- Each mapping focusses on a single attribute
                           targetAttributeName - The name of the attribute that will appear in the assertion
                           sourceType - One of user, static, or composite
                            * user - The name of an existing attribute on the user's object
                            * static - A static value that does not change regardless of the user
                            * composite - A mixture of user and static, allowing for one SAML attribute to be comprised of other attributes and static text.  Attributes are market as "${attributename}"
                           targetAttributeSource - The value to be used based on the sourceType
                         -->
                            <mapping targetAttributeName="uid" targetAttributeSource="uid" sourceType="user"/>
                        </mappings>

                        <!-- Trusts establish a path between the IdP and SP/RP -->
                        <trusts>
                         <!-- The name of the trust, should line up with the entityID in the metadata -->
                            <trust name="https://mysp.partner.com/service/acs">
                             <!-- Where to post assertions to, OpenUnison only supports the HTTP-Post binding -->
        <param name="httpPostRespURL" value="https://mysp.partner.com/service/acs"/>

        <!-- The name of the certificate that signed requests will be signed with -->
        <param name="spSigKey" value="verify-test"/>

        <!-- The name of the certificate that all outbound requests will be encrypted with -->
        <param name="spEncKey" value=""/>

        <!-- If no nameIDFormat is specified in the authnRequest, use this as the default -->
        <param name="defaultNameId" value="urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified"/>

        <!-- if no defaultAuthnContextClassRef is specified in the authenRequest is specified use this one by default -->
        <param name="defaultAuthCtx" value="urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport"/>

        <!-- Determines if the SP requires signed assertions -->
        <param name="signAssertion" value="false"/>

        <!-- Determines if the SP requires signed responses -->
        <param name="signResponse" value="true"/>

        <!-- Determines if the SP requires encrypted assertions -->
        <param name="encAssertion" value="false"/>

        <!-- Mapping of the nameidFormat=attribute to define which user attribute to use as the source of the nameID, may be listed multiple times for multiple nameIDFormats -->
        <param name="nameIdMap" value="urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified=uid"/>

        <!-- Mapping from the authnContextClassRef to the authentication chain name used to authenticate the user -->
        <param name="authCtxMap" value="urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport=Default Login Form"/>
                            </trust>
                        </trusts>
                    </idp>
                </url>
            </urls>

            <!-- The cookie configuration determines how sessions are managed for this application -->
            <cookieConfig>
             <!-- The name of the session cookie for this application.  Applications that want SSO between them should have the same cookie name -->
                <sessionCookieName>tremolosession</sessionCookieName>

                <!-- The domain of component of the cookie -->
                <domain>unison.enterprise.com</domain>
                <scope>-1</scope>
    <!-- The URL that OpenUnison will interpret as the URL to end the session -->
                <logoutURI>/logout</logoutURI>

                <!-- The name of the AES-256 key in the keystore to use to encrypt this session -->
                <keyAlias>session-tremolosession</keyAlias>

                <!-- If set to true, the cookie's secure flag is set to true and the browser will only send this cookie over https connections -->
                <secure>false</secure>

                <!-- The number of seconds that the session should be allowed to be idle before no longer being valid -->
                <timeout>0</timeout>
            </cookieConfig>
        </application>

Custom Authorizations

OpenUnison supports custom authorization rules that implement the com.tremolosecurity.proxy.az.CustomAuthorization interface. Custom authorizations are defined and then referenced by name in other areas of the OpenUnison configuration. Below is an example of a custom authorization rule configuration:

<customAzRules>
 <!-- The name is used to reference the rule configuration -->
 <!-- The className is the implementation of com.tremolosecurity.proxy.az.CustomAuthorization -->
    <azRule name="Manager1" className="com.tremolosecurity.provisioning.az.ManagerAuthorization">
     <!-- The params tag may be listed multiple times with the same name for multi-value parameters -->
        <params name="numLevels" value="1"/>
        <params name="managerID" value="manager"/>
        <params name="managerIDIsDN" value="true"/>
        <params name="allowLowerManagers" value="false"/>
    </azRule>
</customAzRules>

When authorizing based on a custom rule, use the name of your rule. If your rule takes parameters, list them after a ! in your constraint. For instance to call the above rule with a parameter Manager1!param1!param2.

Provisioning Services

OpenUnison’s provisioning services provide a power and robust solution for user management both on a single application or as a common identity service. OpenUnison’s platform provides:

  • Workflow Services - May be used with, or without a stateful audit database

  • Queue Integration - Both for reliability and for scalability

  • Scheduler - Build on the Quartz open source scheduler, jobs may be configured across a cluster

  • Provisioning Targets - Systems that will receive provisioning requests

  • Organizations - A way to organize workflows and reports

  • Reports - A simple engine for providing report data based on the audit database

  • URLs - Generate links for users based on their authorizations

  • Audit Database - Track what requests were made, what approvals were made, etc.

These services can be mixed and matched to provide the identity services needed for your needs.

Workflows

Workflows in OpenUnison are comprised of a base set of tasks and a custom task that allows for any custom logic that may be needed. Each workflow is the coding of business logic into an XML format. Each workflow should have a specific set of outcomes that align to a business need. For instance a workflow could be created to add a user to a directory, or add a user to a group. There are two concepts with workflows that need to be stressed:

  1. Workflows only act on the attributes that are either passed into the workflow or loaded in a task in the workflow. For instance if you are running a just-in-time provisioning workflow you may load attributs for a SAML2 assertion. If adding a user to a group you may only care about the user’s unique identifier and the group being added.

  2. Workflows can either run either synchronously OR asynchronously. When running a request through Scale for instance, you’ll want the reliability that comes from leveraging the queue system however for a Just-in-time provisioning workflow you’ll prefer to be synchronous so you can predict when changes to the user’s accounts will occur.

The workflow format its self uses a tree based structure to define the flow. This leads to simpler implementations that can easily be scripted for large numbers of workflows that follow the same pattern. We specifically did NOT go with a common workflow language such as BPEL or BPM because the power that those XML formats come with also vastly increases the complexity. We wanted something that didn’t require a GUI to write and could easily be scripted to create multiple workflows easily.

Some tasks will have sub tasks. With the exception of the mapping task, all tasks that can have sub tasks can have one set of tasks for success and another for failure. The provides a way to perform actions depending on the outcome of a task. For instance if an approval task fails, a user can be removed from a resource.

Several tasks can include request parameters in their configuration values. This is useful when using a workflow as a template. Any name/value pair in the workflow’s request object can be included between $. For instance, to include the value of an entry in the request object called "groupName" in your workflow’s addGroup would specify "$groupName$" (no double quotes) for the name:

<addGroup name="$groupName$" />

Tasks that take parameters can take any number of parameters and can mix parameters with static text. If a configuration option supports parameters, it will be marked as such in the task documentation below.

Workflows can also be dynamic by adding the dynamicConfiguration block to a workflow. When a dynamic workflow is loaded by the load workflow web services OpenUnison will return a version of the workflow with pre-set parameters. For instance if 10 LDAP groups are returned by the dynamic workflow the service will return 10 instances of this workflow with the parameters pre-filled. This reduces the need to build multiple workflows.

provision

This task is used to push user data to a provisioning target. This task does NOT support sub tasks.

<!-- sync - If true, then the target will update the object in the target to match exactly the current user?s object; potentially removing attributes and entitlements on the user?s object in the target. If false, then only the attribute values on the user?s object will be pushed to the target, in essence ?overlaying? it onto the provisioning target
     target - The name of the provisioning target
     setPassword - If set to true this will create a password on the user. Note that not all targets support password, defaults to false
     onlyPassedInAttributes - If true, then the provisioning task will ignore any attributes that are listed in the target configuration but NOT explicitly added to the user object in the workflow
     -->

<provision sync="false" target="ldap2" setPassword="false" onlyPassedInAttributes="false" >
          <!-- List of attributes to be examined in this step, omit or leave the list empty to allow all of the attributes specified in the target configuration -->
          <attributes>
            <value>l</value>
            <value>cn</value>
            <value>uid</value>
            <value>sn</value>
          </attributes>
</provision>
ifNotUserExists

This task will execute sub tasks if-and-only-if there is not a user in the internal virtual directory that matches the value of the attribute specified in the current user?s context.

<!--
  target - The name of the provisioning target to search
  uidAttribute - The name of the attribute that contains the user's unique ID
-->
<ifNotUserExists target="ldap2" uidAttribute="uid">
 <onSuccess>
   <customTask className="com.tremolosecurity.test.util.TestTask">
    <param name="configVal" value="replacecn"/>
    <param name="attrName" value="cn" />
   </customTask>
   <addGroup name="linkedSAMLUsers" />
   <addAttribute name="givenName" value="addedAttrib"/>
   <provision sync="false" target="ldap2" />
   <resync keepExternalAttrs="false" />
  </onSuccess>
  <onFailure>
  </onFailure>
</ifNotUserExists>
addAttribute

Adds a static attribute to the user’s object or to the workflow’s request object

<!--
  name - The name of the attribute to add, supports parameters
  value - The attribute value, supports parameters
  remove - If true and the value is empty, remove the entire attribute; if true and the value is set, remove only that value of the attribute
  addToRequest - If true the attribute is added to the request object instead of the
-->
<addAttribute name="givenName" value="addedAttrib" remove="false" addToRequest="false" />
addGroup

Adds the named group to the user. NOTE - The group name is NOT a distinguished name, its a name that will be mapped to a group in a provisioning target. This usually means mapping to the cn attribute.

<!--
  name - The name of the group to add, supports parameters
  remove - If true, removes the group
-->
<addGroup name="linkedSAMLUsers" remove="true"/>
approval

The approval step provides for a user to act on a request. and provide either their consent or to disapprove of the request. Each approval can have a set of authorization rules to determine who can make the approval. In addition to authorizations escalation rules can be defined that let the authorization rules change. Finally, if all escalations are exhausted a failure policy can be defined to allow for a request to be re-assigned or rejected.

<!--
  label - The description used to display this approval
-->
<approval label="Approve Access to LDAP">
    <!-- Provides an email template to be sent to approvers, using ${attributeName}
         will add the attribute from the user with the given name to the email.  Supports parameters.
    -->
    <emailTemplate>
      ${givenName},

      You have open approvals

    </emailTemplate>

    <!-- List of authorization rules to determine who can act on this request, constraints supports parameters -->
    <approvers>
      <rule scope="group" constraint="cn=staticgroup,ou=internal,ou=GenericLDAP,o=Tremolo" />
    </approvers>

    <!-- If a request isn't handled in a certain amount of time, how should it be managed? -->
    <escalationPolicy>
      <!-- If no approvers are available, what to do -->
      <!-- action - Either "leave" to do nothing, or "assign" to assign the request to the user(s) defined in the azRules
      <escalationFailure action="assign">
        <azRules>
          <rule scope="filter" constraint="(l=NY)" />
        </azRules>
      </escalationFailure>

      <!-- Define when this request should be escalated
          executeAfterTime - The number of units to wait until a request is escalated
          executeAfterUnits - The unit of time (sec,min,hour,day,month,year)
          validateEscalationClass - Class that is called when verifying an escalation should execute, must implement com.tremolosecurity.proxy.az.VerifyEscalation
      -->
      <escalation executeAfterTime="5" executeAfterUnits="sec" validateEscalationClass="com.tremolosecurity.test.provisioning.escalate.TestOverrideVerify">
        <!-- for each escalation, define which users will be able to authorize the request -->
        <azRules>
          <rule scope="dn" constraint="uid=sslUser7,ou=internal,ou=GenericLDAP,o=Tremolo" />
        </azRules>
      </escalation>
    </escalationPolicy>
    <onSuccess>
      <!-- child processes to be called when a request is approved -->
      <callWorkflow name="provision"></callWorkflow>
    </onSuccess>
    <onFailure>
    </onFailure>
  </approval>
customTask

This task provides a mechanism by which custom logic may be added to a workflow. See individual custom tasks for parameters that support parameters.

<!--
  className - The name of the class that implements the com.tremolosecurity.provisioning.util.CustomTask interface
-->
<customTask className="com.tremolosecurity.test.util.TestTask">
  <!-- Each param tag is a name/value pair for initialization.  If a param tag with the same name is listed multiple times its treated as a multi-valued attribute -->
  <param name="configVal" value="replacecn"/>
  <!-- for large values the parameter value can be put between the tags -->
  <param name="attrName">
<![CDATA[
I don't need to be escaped for "&", "<", or ">"
]]>]
  </param>

</customTask>
delete

The delete task will delete a user in an individual provisioning target

<!--
  target - The name of the provisioning target to delete the user from
-->
<delete target="ldap2" />
ifAttrExists

This task will run if a user object in the workflow has a particuler attribute, regardless of the value.

<!--
  name - the name of the attribute to look for, supports parameters
-->
<ifAttrExists name="someAttr">
  <onSuccess>
    <addGroup name="linkedSAMLUsers" />
    <mapping  strict="true">
      <map>
        <mapping targetAttributeName="uid" sourceType="user" targetAttributeSource="uid"/>
        <mapping targetAttributeName="cn" sourceType="user" targetAttributeSource="cn"/>
        <mapping targetAttributeName="sn" sourceType="user" targetAttributeSource="sn"/>
      </map>
      <onSuccess>
        <provision sync="false" target="ldap2" />
        <resync keepExternalAttrs="false" />
      </onSuccess>

    </mapping>
  </onSuccess>
  <onFailure>
  </onFailure>
</ifAttrExists>
ifAttrHasValue

Use this tag when checking for a certain attribute value before executing a task or tasks.

<!--
  name - The name of the attribute to check, supports parameters
  value - The value the attribute must have, supports parameters
-->
<ifAttrHasValue name="l" value="NY">
  <onSuccess>
  <addGroup name="linkedSAMLUsers" />
  <mapping  strict="true">
    <map>
      <mapping targetAttributeName="uid" sourceType="user" targetAttributeSource="uid"/>
      <mapping targetAttributeName="cn" sourceType="user" targetAttributeSource="cn"/>
      <mapping targetAttributeName="sn" sourceType="user" targetAttributeSource="sn"/>
    </map>
    <onSuccess>
    <provision sync="false" target="ldap2" />
    <resync keepExternalAttrs="false" />
    </onSuccess>
  </mapping>
  </onSuccess>
  <onFailure>
  </onFailure>
</ifAttrHasValue>
mapping

The mapping task will create a copy of the user object based on the mapping rules. Any changes made inside of the mapping to the user object will NOT affect the user object in the workflow outside of the mapping. Unlike other tasks that have sub-tasks, mappings can only have "onSuccess" sub tasks. Anything in "onFailure" will be ignored.

<!--
  strict - If true, the mapped user object will ONLY contain attributes explicitly named in the mapping.  If false, then any attribute not mentioned will be added to the user object with their present value.
-->
<mapping  strict="true">
  <map>
    <!--
      Each mapping maps from a targetAttributeSource to the targetAttributeName
      targetAttributeName - The name of the attribute in the new user object, supports parameters
      targetAttributeSource - The value to be used for the new attribute, supports parameters
      sourceType - Describes what the targetAttributeSource is.  Possible values are:
        static - Takes the value from targetAttributeSource as is
        user - Takes the existing value from the attribute named in targetAttributeSource
        composite - Use a composite of multiple attributes and static values by containing attribute names in ${attributeName}
        custom - targetAttributeSource is the name of a class that implements com.tremolosecurity.provisioning.mapping.CustomMapping
    -->
    <mapping targetAttributeName="uid" sourceType="user" targetAttributeSource="uid"/>
    <mapping targetAttributeName="cn" sourceType="composite" targetAttributeSource="${givenName} ${sn}"/>
    <mapping targetAttributeName="sn" sourceType="user" targetAttributeSource="sn"/>
  </map>
  <onSuccess>
    <!-- Child tasks -->
    <provision sync="false" target="ldap2" />
    <resync keepExternalAttrs="false" />
  </onSuccess>
</mapping>
notifyUser

This task provides a way to send an email to the requester of the workflow. This can be used to notify the user of a successful execution, request more information, etc. Emails are sent from the server specified in the approvals section of the configuration. If the requester is different then the subject of the workflow, then the requester is notified UNLESS the unison.sendToSubject property is stored in the workflow’s request object. The value does not matter.

<!--
  subject - The subject of the email notification, supports parameters
  mailAttribute - The name of the attribute that stores the user's email address
  contentType - Optional - Specify the MIME content type of the email, ie text/plain or text/html
-->
<notifyUser subject="Request succeeded" mailAttrib="mail" contentType="text/plain">
  <!-- The message to send.  Attributes may be added between ${}, supports parameters -->
  <msg>
    ${giveName},

    Your request has been approved
  </msg>
</notifyUser>
resync

When executing a just-in-time provisioning workflow, for instance when using identity federation, once the user?s object is created in downstream targets the user?s object in Unison will need to be ?refreshed?. This task updates the internal Unison object.

<!--
  keepExternalAttrs - If true, the user object will maintain any attributes that were not loaded from the internal virtual directory.
  changeRoot - If true, OpenUnison will reload the user's object from a different search base then the one that was originally used to find the user
  newRoot - If changeRoot is checked, the root of the virtual directory to use to find the user; supports parameters
-->
<resync keepExternalAttrs="false" changeRoot="true" newRoot="o=Tremolo" />
callWorkflow

This task allows for another workflow to be called. This allows for the creation of modular workflows. For instance a modular workflow can be created that requires 2 approvals before provisioning to a resource. This workflow can be included in a self-service request from the portal and a helpdesk application with the same results without having to duplicate the workflow.

<!--
  name - The name of the workflow to call
-->
<callWorkflow name="provision" />

Queueing

Unison uses a message queue for all asynchronous provisioning operations. The use of a message queue allows Unison to ensure that workflows are completed, even if targets such as directories and databases are down. Unison encrypts all messages sent to the queue for increased security. There are two modes that Unison can use:

  1. Internal Queue

  2. External Queue

When Unison uses an internal queue, the queue is local to the Unison instance. This provides a simpler deployment model, but Unison servers are not able to failover in case of a failed provisioning task or spread the load across multiple servers. This is the default mode that Unison uses and no actions need to be taken to get this working after installation.

Leveraging an external queue allows for high availability across Unison instances if a workflow fails on one server it can be picked up by another server. External queues can also increase the volume of operations Unison can handle because tasks do not need to be processed on a single box. Any message queue that supports JMS 2.0 is supported by Unison. See the certification matrix for tested and certified queues.

<!--
  isUseInternalQueue - Determines if Unison should use the internal ActiveMQ system for message management or an external system.
  encryptionKeyName - The session key used to encrypt all task messages.
  connectionFactory - Implementation of javax.jms.ConnectionFactory, only if isUserInternalQueue is false
  maxProducers - Maximum number of threads generating messages
  maxConsumers - Maximum number of threads consuming messages
  taskQueueName - The name of the queue for managing workflow tasks, if multiTaskQueues is true, use {x} to represent the queue number
  smtpQueueName - The name of the queue for managing emails generated by Unison
  multiTaskQueues - true if multiple task queues are available
  numQueues - If multiTaskQueues is true, the number of queues available for tasks
  keepAliveMillis - Milliseconds between keepalive attempts for each connections.  Defaults to 60000 (60 seconds)
-->
<queueConfig isUseInternalQueue="false" encryptionKeyName="session-queues" connectionFactory="org.apache.activemq.ActiveMQConnectionFactory" maxProducers="5" maxConsumers="5" taskQueueName="TremoloUnisonTaskQueue-{x}" smtpQueueName="TremoloUnisonSMTPQueue" multiTaskQueues="true" numQueues="3" keepAliveMillis="60000" >
  <!--
    Each param tag maps to a setter method on the connectionFactory class.  For instance the brokerURL param tag maps to org.apache.activemq.ActiveMQConnectionFactory.setBrokerURL(String) method
  -->
  <param name="brokerURL" value="tcp://amq.tremolo.lan:61616/" />
</queueConfig>

Queue Listeners

In addition to workflows, Unison can provide que services to other components in Unison as well. For instance a schedule task that needs to perform work on user accounts would want to put the work load for each account in a queu, allowing the payload to be worked on by a different server in a cluster and not block the scheduled task from completing. All queue messages are encrypted with the same encryption key as workflow tasks. To implement a task, extend the com.tremolosecurity.provisioning.core.UnisonMessageListener class. Pre-built message listeners are documented in the Unison Message Listeners section.

<listeners>
  <!-- UNCOMMENT FOR LOADING MESSAGE LISTENERS FROM KUBERNETES OBJECTS -->
  <!--

  <dynamicListeners enabled="true" className="com.tremolosecurity.provisioning.listeners.LoadQueueListenersFromK8s">
    <params name="k8starget" value="k8s" />
    <params name="namespace" value="#[K8S_OPENUNISON_NS:openunison]" />
  </dynamicListeners>
  -->

  <!-- Each listener waits for a message to be received on the named queue using the class named. -->
  <listener className="com.tremolosecurity.provisioning.listeners.UpdateApprovalAZListener" queueName="rebaseQueue"></listener>
  <listener className="com.tremolosecurity.provisioning.listeners.AutoFailApprovalListener" queueName="failQueue"></listener>
</listeners>
Dynamic Message Listeners

Message listeners can be loaded dynamically from Kubernetes by creating MessageListener objects in the openunison namespace. Updating/creating/deleting these objects causes running OpenUnisons to pick up the changes without a restart. Here is an example MessageListener:

apiVersion: openunison.tremolo.io/v1
kind: MessageListener
metadata:
  annotations:
    meta.helm.sh/release-name: k8s-mgmt
    meta.helm.sh/release-namespace: openunison
  labels:
    app.kubernetes.io/managed-by: Helm
  name: rebase-queue
  namespace: openunison
spec:
  className: com.tremolosecurity.provisioning.listeners.UpdateApprovalAZListener
  params: []
  secretParams: []

The configuration matches 1-1 with the XML based configuration for each message listener with one major difference. Sensitive information, such as passwords, should NOT be stored in the CR directly. Secret information should be stored in a Secret object and referenced in the secretParams section as in this example:

spec:
  .
  .
  .
  secretParams:
    # name of the configuraiton option for the target
    - name: mysecret
      # The name of the Secret object
      secretName: somesecret
      # The key in the data section of the Secret
      secretKey: dataelement

Scheduler

Unison includes an integrated scheduler that can be used to schedule task to run on a periodic basis. Unison can either use a local, in memory scheduler or a database scheduler. The database scheduler is recommended for production deployments to ensure that scheduled tasks are only executed once. Unison uses the Quartz scheduler (http://quartz-scheduler.org).

<!--
  useDB - If true, use a database to manage jobs. NOTE: prior to starting Unison the database must be initialed per Quartz' documentation
  threadCount - The number of threads the scheduler should use. Must be at least 3
  instanceLabel - A descriptive label for this scheduler
  instanceIPMask - This field is used to determine which IP address to use on the system to identify the particular instance
-->
<scheduler useDB="true" threadCount="3" instanceLabel="testing" instanceIPMask="127">



  <!-- This section only needed if useDB is true -->
  <!--
    delegateClassName - The delegate to use to work with the database
    driver - The JDBC Driver for the database
    url - The JDBC URL
    user - User to connect to database with
    password - Password to connect to database with
    maxConnections - Maximum number of connections to the database
    validationQuery - A query that can be used to verify the connection is still active
  -->
  <scheduleDB
    delegateClassName="org.quartz.impl.jdbcjobstore.StdJDBCDelegate"
    driver="com.mysql.jdbc.Driver"
    url="jdbc:mysql://endor.tremolo.lan/quartzDB"
    user="quartz"
    password="start123"
    maxConnections="10"
    validationQuery="SELECT 1"
  />

  <!-- UNCOMMENT FOR DYNANMICLY LOADING JOBS IN KUBERNETES -->
  <!--
  <dynamicJobs enabled="true" className="com.tremolosecurity.provisioning.jobs.LoadJobsFromK8s">
    <params name="k8starget" value="k8s" />
    <params name="namespace" value="#[K8S_OPENUNISON_NS:openunison]" />

  </dynamicJobs
  -->

  <!-- Each job extends com.tremolosecurity.provisioning.scheduler.UnisonJob -->
  <!--
    className - The name of the class
    name - Descriptive name for this scheduled job
    group - Descriptive name for a group of jobs
  -->
  <job className="com.tremolosecurity.provisioning.scheduler.jobs.UpdateApprovalAz" name="resetAllowedApprovers" group="testing">
    <!-- All jobs use a cron based scheduler - http://www.adminschoice.com/crontab-quick-reference -->
    <cronSchedule
      seconds="0/9"
      minutes="*"
      hours="*"
      dayOfMonth="*"
      month="*"
      dayOfWeek="?"
      year="*"
    />
    <!-- Each parameter is passed into the task on execution, params with the name name listed multiple times are treated as a multi-valued attribute -->
    <param name="queueName" value="rebaseQueue" />
  </job>
  <job className="com.tremolosecurity.provisioning.scheduler.jobs.AutoFail" name="autoFail" group="autoFail">
    <cronSchedule
      seconds="0/20"
      minutes="*"
      hours="*"
      dayOfMonth="*"
      month="*"
      dayOfWeek="?"
      year="*"
    />
    <param name="queueName" value="failQueue" />
    <param name="approver" value="autoFail" />
    <param name="message" value="failed : ${reason}" />
  </job>
</scheduler>
Dynamic Jobs

Scheduled jobs can be loaded dynamically from Kubernetes by creating OUJob objects in the openunison namespace. Updating/creating/deleting these objects causes running OpenUnisons to pick up the changes without a restart. Here is an example OUJob:

apiVersion: openunison.tremolo.io/v1
kind: OUJob
metadata:
  annotations:
    meta.helm.sh/release-name: k8s-mgmt
    meta.helm.sh/release-namespace: openunison
  creationTimestamp: "2020-12-16T01:14:24Z"
  generation: 1
  labels:
    app.kubernetes.io/managed-by: Helm
  name: remind-approvers
  namespace: openunison
spec:
  className: com.tremolosecurity.provisioning.scheduler.jobs.UpdateApprovalAz
  cronSchedule:
    dayOfMonth: '*'
    dayOfWeek: '?'
    hours: "9"
    minutes: "0"
    month: '*'
    seconds: "0"
    year: '*'
  group: management
  params:
  - name: message
    value: The request %L has been open for %D days, please login to act on this request
  - name: days
    value: "7"
  - name: mailAttributeName
    value: mail
  secretParams: []

The configuration matches 1-1 with the XML based configuration for each job with one major difference. Sensitive information, such as passwords, should NOT be stored in the CR directly. Secret information should be stored in a Secret object and referenced in the secretParams section as in this example:

spec:
  .
  .
  .
  secretParams:
    # name of the configuraiton option for the target
    - name: mysecret
      # The name of the Secret object
      secretName: somesecret
      # The key in the data section of the Secret
      secretKey: dataelement

Provisioning Targets

Provisioning Targets are how Unison pushes, updates and disables account information in individual systems. Targets are utilized inside of workflows (covered in the next section) to manage account information. Custom targets can be created as well, to create a custom target consult the SDK. For specific information configuring targets see the Target Configuration section in this guide. Every target has a mapping associated with it. This mapping makes the target ?self-contained?, so it may be used across multiple workflows.

See the Provisioning Targets section in the configuration reference for the currently supported OpenUnison provisioning targets.

<targets>

  <!-- UNCOMMENT FOR DYNAMIC TARGETS -->
  <!--

  <dynamicTargets enabled="true" className="com.tremolosecurity.provisioning.targets.LoadTargetsFromK8s">
    <params name="k8starget" value="k8s" />
    <params name="namespace" value="#[K8S_OPENUNISON_NS:openunison]" />

  </dynamicTargets>

  -->
  <!--
    name - A unique name for this target, how it is referenced throughout the configuration and code base
    className - Name of the class backing this target, must implement the com.tremolosecurity.provisioning.core.UserStoreProvider interface
  -->
  <target name="ldap2"
    className="com.tremolosecurity.provisioning.core.providers.LDAPProvider">
    <!-- Each parameter is passed into the target on initialization.  Param tags with the name name listed multiple times are treated as a multi-valued attribute -->
    <params>
      <param name="objectClass" value="inetOrgPerson" />
      <param name="host" value="127.0.0.1" />
      <param name="port" value="10983" />
      <param name="adminDN" value="cn=admin,dc=domain,dc=com" />
      <param name="adminPasswd" value="manager" />
      <param name="dnPattern" value="uid=${uid},ou=internal,dc=domain,dc=com" />
      <param name="searchBase" value="dc=domain,dc=com" />
      <param name="userIDAttribute" value="uid"/>
      <param name="useSSL" value="false"/>
      <param name="maxCons" value="10"/>
      <param name="threadsPerCons" value="10"/>

    </params>

    <!--
      Each targetAttribute maps from a source to the name
      name - The name of the attribute in the target
      source - The value to be used for the new attribute
      sourceType - Describes what the source is.  Possible values are:
        static - Takes the value from targetAttributeSource as is
        user - Takes the existing value from the attribute named in targetAttributeSource
        composite - Use a composite of multiple attributes and static values by containing attribute names in ${attributeName}
        custom - targetAttributeSource is the name of a class that implements com.tremolosecurity.provisioning.mapping.CustomMapping
      targetType (optional) - provides an optoinal mapping to a datatype.  Defaults to string
        string
        int
        long
        date
        timestamp
    -->
    <targetAttribute name="uid" sourceType="user"
      source="TREMOLO_USER_ID" />
    <targetAttribute name="sn" sourceType="user"
      source="sn" />
    <targetAttribute name="l" sourceType="user"
      source="l" />
    <targetAttribute name="cn" sourceType="user"
      source="cn" />
    <targetAttribute name="givenName"
      sourceType="user" source="givenName" />
  </target>
</targets>
Dynamic Targets

Targets can be dynamically loaded in Kubernetes by creating Target objects in the openunison namespace. Here is an example Target:

apiVersion: openunison.tremolo.io/v1
kind: Target
metadata:
  annotations:
    meta.helm.sh/release-name: managed-cluster
    meta.helm.sh/release-namespace: openunison
    app.kubernetes.io/managed-by: Helm
  name: k8s-managed
  namespace: openunison
spec:
  className: com.tremolosecurity.unison.openshiftv3.OpenShiftTarget
  params:
  - name: url
    value: https://k8sapi2.apps.192.168.2.148.nip.io
  - name: useToken
    value: "true"
  - name: certificate
    value: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUQvekNDQXVlZ0F3SUJBZ0lHQVhaRGNTN0NNQTBHQ1NxR1NJYjNEUUVCQ3dVQU1JR1ZNU293S0FZRFZRUUQKRENGck9ITmhjR2t5TG1Gd2NITXVNVGt5TGpFMk9DNHlMakUwT0M1dWFYQXVhVzh4RXpBUkJnTlZCQXNNQ2t0MQpZbVZ5Ym1WMFpYTXhEakFNQmdOVkJBb01CVTE1VDNKbk1STXdFUVlEVlFRSERBcE5lU0JEYkhWemRHVnlNUmt3CkZ3WURWUVFJREJCVGRHRjBaU0J2WmlCRGJIVnpkR1Z5TVJJd0VBWURWUVFHRXdsTmVVTnZkVzUwY25rd0hoY04KTWpBeE1qQTRNVGMwTURVNVdoY05NakV4TWpBNE1UYzBNRFU1V2pDQmxURXFNQ2dHQTFVRUF3d2hhemh6WVhCcApNaTVoY0hCekxqRTVNaTR4TmpndU1pNHhORGd1Ym1sd0xtbHZNUk13RVFZRFZRUUxEQXBMZFdKbGNtNWxkR1Z6Ck1RNHdEQVlEVlFRS0RBVk5lVTl5WnpFVE1CRUdBMVVFQnd3S1RYa2dRMngxYzNSbGNqRVpNQmNHQTFVRUNBd1EKVTNSaGRHVWdiMllnUTJ4MWMzUmxjakVTTUJBR0ExVUVCaE1KVFhsRGIzVnVkSEo1TUlJQklqQU5CZ2txaGtpRwo5dzBCQVFFRkFBT0NBUThBTUlJQkNnS0NBUUVBMTZvTHlvRHcrcHVId1hmTHNOSUFVMUMzYjJVdWpTWGdzdDNmCnZrQU50aTQyWEV2K3ZaMU9BV1oyTzBQL3dXa2NMb0NQcVBWdUZsRGx4cjNEdTBIWnkzekxUU2Zjem9IYURGczYKdk54VGpuM3poQi9XelpkbEkyeVl1K0VSY0NkTGFBOGYzMmFzUjR5YUNkV1Nvc29TVEE2NkhDVm95NFAvcm90aApyU0JSV00rdTFjMnVHYzBocDVSNytBRDY2U1ljUXNLYUJJQ1FWamd4YmFiRW1zaTFzV1M0dFcwRGlWZzUyMTNjCmNPNWgxMmpPN1A0cjd4dHd5ZkNQbGwrYzFEMnhsa2xSSUNGUEZyZHkzL3d3UENwSCtrNU9hQ3hNY1MyZ0lHUFcKdGtFUElDR2IrNmY0bkFsMktueGlCQTJKR3FLVnMrZE9OMzRBRzAvT1JEZ2txU214bXdJREFRQUJvMU13VVRCUApCZ05WSFJFRVNEQkdnaUZyT0hOaGNHa3lMbUZ3Y0hNdU1Ua3lMakUyT0M0eUxqRTBPQzV1YVhBdWFXK0NJV3M0CmMyRndhVEl1WVhCd2N5NHhPVEl1TVRZNExqSXVNVFE0TG01cGNDNXBiekFOQmdrcWhraUc5dzBCQVFzRkFBT0MKQVFFQWFxc09DRFNwZWtONUp5dExleDFoNE5XQkpPblYwL2hDTnpNeWZvbkVmYUpjNDA2R09HUGFmTG8xNEd6ZApsbjVJZVVHQlJZL1VhMTdmUHpxKzhYQnVOMFEyTThNNWVNbEZMQ1lubXN1dG5TbHEyN0VsV05rQk5tbkZXZUIxCkM5bS9Sa1BoNG54THphYld4cUFtY3Bxb3JZWjNZdmFKU3lBSThqM2llbXBScUMyWTNEWmxqek1DSVFSNlk1bjYKcGM0aCtpQzAxT2lrV1JVdG10a1Y0MjBYVWt5am94ZzFrVHk2ODI5K1MrdGZJa1VJaitZaFZOZUVrei9iR0dmYwpRempXMWtkYUJucXNzcVRmdmt5UjRWR1NYVHNHNExjakE0QlBEOEFDR0NWcGVxelJ2NTI1QXIwMCt3R0U5d1ZrCnkrRWhSVHlTRjk3eWtSZXFYWURuREtSRG9RPT0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQ==
  - name: tokenType
    value: oidc
  - name: oidcIdp
    value: remotek8s
  - name: oidcSub
    value: openunison
  - name: oidcAudience
    value: kubernetes
  - name: label
    value: Managed Cluster
  - name: gitUrl
    value: ""
  secretParams: []
  targetAttributes:
  - name: fullName
    source: displayName
    sourceType: user

The configuration matches 1-1 with the XML based configuration for each target with one major difference. Sensitive information, such as password, should NOT be stored in the CR directly. Secret information should be stored in a Secret object and referenced in the secretParams section as in this example:

spec:
  .
  .
  .
  secretParams:
    # name of the configuraiton option for the target
    - name: mysecret
      # The name of the Secret object
      secretName: somesecret
      # The key in the data section of the Secret
      secretKey: dataelement

When a Target object is updated, OpenUnison will reload it automatically without having to restart the process. If a Secret is updated, you still need to update the Target. The easiest way to do this is apply an annotation.

Organizations

Organizations provide a way to organize workflows in a hierarchy. In a small deployment a single organization may be all thats needed, but in larger deployment it can be difficult to organize workflows. By creating organizations users can navigate through a tree to find the workflow that they need. In addition, organizations provide a mechanism to authorize users to be able to request certain workflows. For instance an organization called "Administrators" may only allow users that are a member of the administrators group to execute workflows. This makes it easier to cut down on extra approvals.

<!--
  The root organization is required and should be open to all users
  name - Descriptive name of the organization
  description - Some text describing the organization
  uuid - A UUID that is unique for this organization.  This is how an organization is referenced in the configuration
  showInPortal - Shows the orgnaization in the front page of ScaleJS, defaults to true
  showInRequestsAccess - Shows the organization on request access page in ScaleJS, defaults to true
  showInReports - Shows the organization on the reports page, defaults to true
-->
<org name="MyOrg" description="MyOrg Enterprise Applications" uuid="687da09f-8ec1-48ac-b035-f2f182b9bd1e">
  <!-- Organizations can be dynamically loaded outside of the static configuration
        enabled - true if dynamic organizations should be enabled
    className - Implementation of com.tremolosecurity.provisioning.orgs.DynamicOrgs
  -->
  <dynamicOrgs enabled="true" className="com.tremolosecurity.provisioning.orgs.LoadOrgsFromK8s">
    <params name="k8starget" value="k8s" />
    <params name="namespace" value="#[K8S_OPENUNISON_NS:openunison]" />

  </dynamicOrgs>
  <!-- Each child organization is nested as an orgs tag with the same attributes as org -->
  <orgs name="User Reports" description="Reports Available to All Users" uuid="1c2cad5b-f62c-491c-84f3-068f6231f053">
    <!-- Each organization can have a set of authorization rules to determine which users can see this organization and anything contined in it -->
    <azRules/>
  </orgs>
  <orgs name="Identity Management Reports" description="Reports only available to the Identity Management Team"   uuid="d17a59d8-11bc-44d4-994c-740194f4fd03">
    <azRules>
    <!--
    Determine if the currently logged in user may access the organization.  If ANY rule succeeds, the authorization succeeds.
    The scope may be one of group, dn, filter, dynamicGroup or custom
    The constraint identifies what needs to be satisfied for the authorization to pass and is dependent on the scope:
      * group - The DN of the group in OpenUnison's virtual directory (must be an instance of groupOfUniqueNames)
      * dn - The base DN of the user or users in OpenUnison's virtual directory
      * dynamicGroup - The DN of the dynamic group in OpenUnison's virtual directory (must be an instance of groupOfUrls)
      * custom - An implementation of com.tremolosecurity.proxy.az.CustomAuthorization -->
      <rule scope="group" constraint="cn=idm-auditreports,ou=groups,o=Tremolo"/>
    </azRules>
  </orgs>
  <azRules/>
</org>

Reports

Unison provides a simple reporting mechanism that can be used to provide reports via the web services API. These reports can be used by Scale or any other client via the API. Reports have the following features:

Headers Section

Data sets can be broken out be groups and include a header section for each data set

Parameters

Unison reports can accept any of the four parameters:
  • currentUser - The currently logged in user

  • userKey - Specify a specific user

  • beginDate - Date for the beginning of a date range

  • endDate - Date for the end of a date range

Authorizations

Reports are categorized into organizations, each having an authorization

<reports>
  <!--
    Each report is assigned to an organization to control access.  If a user has access to an organization, they can view the reports in that organization.
    orgID - The uuid of the org(s) tag that the report is associated with
    name - A descriptive name for the report
    description - A few words about the report
    groupings - If true, the report results will be broken up into multiple sections when the field identified by the groupBy attribute changes.
    groupBy - Determines which attribute causes a new grouping in the report when it chages
  -->
  <report orgID="1c2cad5b-f62c-491c-84f3-068f6231f053" name="My Open Requests" description="List of your currently open requests and the approvers responsible for acting on them" groupBy="id" groupings="true">
    <!-- Any of the parameters described above, may be listed multiple times -->
    <paramater>currentUser</paramater>
    <!-- The SQL that drives the report.  Each field should be aliased using "AS" to match with a headerField or dataField.  Parameters are marked using JDBC syntax using a question mark (?).  Parameters are used in the order defined in the previous parameter tag -->
    <sql>select  approvals.id,approvals.label AS Approval ,
                 approvals.createTS AS `Approval Opened`,
                 workflows.name AS `Workflow Name`,
                 workflows.requestReason AS `Request Reason`,
                 concat(users.givenName,' ',users.sn) as `Subject Name`,
                 users.mail as `Subject Email`,
                 approvers.givenName as `First Name`,
                 approvers.sn as `Last Name`,approvers.mail as `Email`
        from approvals inner join workflows on approvals.workflow=workflows.id
                       inner join users on workflows.userid=users.id
                       inner join allowedApprovers on approvals.id=allowedApprovers.approval
                       inner join approvers on approvers.id=allowedApprovers.approver
        where users.userKey=? AND approvedTS is null
        order by approvals.createTS ASC, approvals.id ASC</sql>
    <!-- Fields that should be displayed in a data set's header. NOTE - fields MUST map to a field that is in the SQL SELECT clause. -->
    <headerFields>Approval</headerFields>
    <headerFields>Subject Name</headerFields>
    <headerFields>Subject Email</headerFields>
    <headerFields>Workflow Name</headerFields>
    <headerFields>Request Reason</headerFields>
    <!-- Fields that should be displayed in a data set. NOTE - fields MUST map to a field that is in the SQL SELECT clause. -->
    <dataFields>First Name</dataFields>
    <dataFields>Last Name</dataFields>
    <dataFields>Email</dataFields>
  </report>
</reports>
Dynamic Reports

Reports can be loaded dynamically from Kubernetes by creating Report objects in the openunison namespace. Updating/creating/deleting these objects causes running OpenUnisons to pick up the changes without a restart. Here is an example Report:

apiVersion: openunison.tremolo.io/v1
kind: Report
metadata:
  annotations:
    meta.helm.sh/release-name: k8s-mgmt
    meta.helm.sh/release-namespace: openunison
  labels:
    app.kubernetes.io/managed-by: Helm
  name: change-log-for-period
  namespace: openunison
spec:
  dataFields:
  - Action
  - Target Type
  - Target
  - Name
  - Value
  description: Changes to all users between the two selected dates
  groupBy: id
  groupings: true
  headerFields:
  - Workflow Name
  - Workflow Label
  - Request Reason
  - Workflow Started
  - Workflow Completed
  - First Name
  - Last Name
  - Email Address
  name: Change Log for Period
  orgId: d2f56bce-b0d4-44fa-8c5d-bd600b0bc589
  parameters:
    beginDate: true
    endDate: true
  sql: "select  (SELECT value FROM userAttributes WHERE name='givenname' AND userid=users.id)
    AS `First Name`, \n        (SELECT value FROM userAttributes WHERE name='sn' AND
    userid=users.id) AS `Last Name`, \n        (SELECT value FROM userAttributes WHERE
    name='mail' AND userid=users.id) AS `Email Address` ,\n        workflows.id, \n
    \       workflows.name as `Workflow Name`,\n        workflows.label AS `Workflow
    Label`,\n        workflows.startTS AS `Workflow Started`,\n        workflows.completeTS
    AS `Workflow Completed`,\n        workflows.requestReason AS `Request Reason`,
    \n        auditLogType.name  AS `Action`,CASE WHEN isEntry = 1 THEN 'Object' ELSE
    'Attribute' END AS `Target Type`,\n        auditLogs.attribute AS `Name`,auditLogs.val
    AS `Value`,\n        targets.name AS `Target` \nfrom users inner join auditLogs
    on users.id=auditLogs.userid \n            inner join auditLogType on auditLogType.id=auditLogs.actionType
    \n            inner join workflows on workflows.id=auditLogs.workflow \n            inner
    join targets on auditLogs.target=targets.id\nwhere workflows.completeTS >= ? and
    workflows.completeTS <= ?\norder by workflows.completeTS ASC ,workflows.id ASC

URLs

While Unison can provision access to applications, its also can be important to be able to tell users about what applications they have access to. To provide this, Unison has a portal URL api that can be configured on this screen. Each URL can be assigned to an organization for situations where there are hundreds of applications a user may have.

Note: in order to include an icon in your URL you must base64 encode a 240x210 PNG file. On *nix this is easily done via the base64 command:

$ cat /path/to/icon.png | base64
Example URL Configuration
<portal>
    <!-- URLs can by dynamically loaded by implementing the com.tremolosecurity.provisioning.portal.DynamicPortalUrls interface
         enabled - if dynamic URLs should be use
         className - The implementation of com.tremolosecurity.provisioning.portal.DynamicPortalUrls interface -->
    <dynamicUrls enabled="true" className="com.tremolosecurity.provisioning.portal.LoadUrlsFromK8s">
      <params name="k8starget" value="k8s" />
      <params name="namespace" value="#[K8S_OPENUNISON_NS:openunison]" />

    </dynamicUrls>
    <!--
      Each url is listed with a urls tag
      label - What users in Scale will see
      url - The URL the browser will run when the user clicks on the icon
      name - A simple name for the URL
      org - The orgUUID of the organization to list this url in if URLs are going to be displayed via the organization tree
      icon - A base64 encoded 240x210 PNG file that will be displayed in Scale
    -->
    <urls label="Reset your password" url="/scale-password/" name="PasswordReset" org="8daa55cd-dd52-4fa9-8edd-09a820811d05" icon="iVBORw0KGgoAAAA...">
        <!--
          The azRules will tell Unison if the link should be displayed to the user
        -->
        <azRules>
            <rule scope="dn" constraint="CN=users,ou=users,o=Tremolo"/>
        </azRules>
    </urls>
</portal>

Audit Database

An audit database is required to allow for users to approve requests. This database will track all requests, approvals (or rejections), attribute changes, etc.

<approvalDB>
  <!-- The Hibernate SQL Dialect -->
  <hibernateDialect>org.hibernate.dialect.PostgreSQL9Dialect</hibernateDialect>
  <!-- The JDBC Driver, must be part of the classpath -->
  <driver>com.mysql.jdbc.Driver</driver>
  <!-- JDBC URL for the database -->
  <url>jdbc:mysql://127.0.0.1:3306/unison</url>
  <!-- User for accessing the database -->
  <user>unison</user>
  <!-- Password for the database -->
  <password>start123</password>
  <!-- Maximum number of connections to the database -->
  <maxConns>10</maxConns>
  <!-- Maxixum number of idle connections -->
  <maxIdleConns>10</maxIdleConns>
  <!-- An optional list of additional properties for Hibernate, may be listed multiple times -->
  <!-- <hibernateProperty name="hibernate.default_schema" value="public" /> -->

  <!-- The name of the attribute on the user that is a unique identifier -->
  <userIdAttribute>uid</userIdAttribute>
  <!-- Query used to verify that the connection is alive -->
  <validationQuery>SELECT 1</validationQuery>

  <!-- The list of attributes to record in the approvers table.  NOTE: each value tag MUST line up with a column created on the approvers table -->
  <approverAttributes>
    <value>uid</value>
    <value>givenName</value>
    <value>sn</value>
    <value>mail</value>
  </approverAttributes>

  <!-- The list of attributes to record in the users table.  NOTE: each value tag MUST line up with a column created on the users table -->
  <userAttributes>
    <value>uid</value>
    <value>givenName</value>
    <value>sn</value>
    <value>mail</value>
  </userAttributes>
  <!-- determines if the audit database should be enabled, not required for using Just-In-Time provisioning -->
  <enabled>true</enabled>
  <!-- The alias of the key used to encrypt all workflows stored in the database -->
  <encryptionKey>session-workflows</encryptionKey>
  <!-- SMTP Server host name -->
  <smtpHost>smtp.gmail.com</smtpHost>
  <!-- SMTP Server port -->
  <smtpPort>587</smtpPort>
  <!-- User for accessing the SMTP server -->
  <smtpUser>donotreply@mydomain.com</smtpUser>
  <!-- Password for the smtp server -->
  <smtpPassword>********</smtpPassword>
  <!-- Subject of emails sent to approvers -->
  <smtpSubject>Awaiting Approvals</smtpSubject>
  <!-- Email address for the "From" field of all emails -->
  <smtpFrom>donotreply@mydomain.com</smtpFrom>
  <!-- Determines if TLS should be used with the SMTP server -->
  <smtpTLS>true</smtpTLS>
  <!-- Set to true if an outbound SOCKS proxy is required for access to the SMTP server -->
  <smtpUseSOCKSProxy>false</smtpUseSOCKSProxy>
  <!-- Host of the SOCKS proxy -->
  <smtpSOCKSProxyHost></smtpSOCKSProxyHost>
  <!-- Port of the SOCKS proxy -->
  <smtpSOCKSProxyPort>0</smtpSOCKSProxyPort>
  <!-- How Unison should identify its self to the SMTP server -->
  <smtpLocalhost></smtpLocalhost>

  <!-- Optional - The mapping file (.hbx.xml) to use if the default mapping doesn't work, must be in the classpath -->
  <hibernateConfig></hibernateConfig>
  <!-- Optional - If true, create tables.  After initial configuration, you may want to disable this depending on the dialect. -->
  <hibernateCreateSchema>true</hibernateCreateSchema>

</approvalDB>
Database Schema

Unison’s provisioning model uses an open table format for storing relationships and audit data from the provisioning process. This format allows for custom reporting as well as storage in any SQL database. The below diagram and descriptions provide information on how these tables relate and provide the baseline for writing reports using you’re favorite reporting tools. These tables are created for you the first time OpenUnison starts.

====== users This table lists the users and certain that attributes that have been processed.

====== userAttributes The userAttributes table contains all of the attributes listed by userAttributes section of the auditDB tag.

====== approvers This table lists the approvers and certain attributes that have been processed. This table should be updated based on the approver attributes to be tracked in reports. The only two fields in this table that are required are id and userKey. Each additional field should be the same name as an attribute in the workflow request. For instance if the givenName, sn and mail attributes are to be tracked then they should have fields in this table called givenName, sn and mail.

====== approverAttributes The approverAttributes table contains all of the attributes listed by approverAttributes section of the auditDB tag.

====== targets This table lists the name of all workflow targets as configured in the administration system. It is automatically populated when Unison is started or the configuration is re-loaded. It should only be used for reporting and should not be updated manually.

====== workflows The main driving table, each row tracks a workflow and is the main table for tracking all workflows.

====== approvals Tracks each approval needed in a workflow. Each workflow’s state is stored in an encrypted and Base64’d object. Once the approval is complete, the workflow object is null.

====== allowedApprovers Link table for determining who can act on an approval. This table is primarily for use by the web service to list who can act on an approval and is populated when the approval is created in the workflow based on the rules configured in the administration system. When the approval is executed, it still checks against the rules configured in the administration system.

====== auditLogType This table is a lookup table for various audit log types. Its populated with the values below.

====== auditLogs This table tracks all changes processed by Unison’s provisioning engine.

====== escalations This table lists all of the escalations for a workflow.

====== workflowParamters This table lists all of the parameters passed into a workflow’s request object. NOTE if an object is added to the request AFTER a workflow starts it is NOT recorded here. This table is meant to identify an individual request in a workflow.

Configuration Reference

MyVD Inserts

For documentation on MyVD and the standard MyVD inserts, see the MyVD documentation at http://myvd.sourceforge.net/. The following inserts are specifically built for Unison, but many of them can be used in a standalone MyVD deployment. All of the below inserts (unless noted) are in the unison-myvd maven module and can be used on its own.

ExternalGroupMembers

The External Group Members insert allows an Active Directory forest to store group members that are not a member of the forest or a trusted forest. This insert requires an attribute to be defined that will store the Unison DN of a user in the specified attribute and merge it with the uniqueMember attribute of the group.

Class Name

com.tremolosecurity.proxy.myvd.inserts.ad.ExternalGroupMembers

externalGroupAttrName

The name of the attribute for storing the DN, must be allowed on the group objectClass

AuthTOTPInsert

The TOTP Authentication insert provides TOTP authentication for users who have been provisioned with a TOTP token using the CreateOTPKey custom provisioning task by adding the current key to the end of the password after a ":". So if the current code is 123456 and your password is "secret" then the correct password would be "secret:123456".

Class Name

com.tremolosecurity.proxy.myvd.inserts.otp.AuthTOTPInsert

attribute

The name of the attribute that stores the token

encryptionKey

The name of the encryption key managed by Unison

window

Number of 30 second windows a key should be valid for

AdminInsert

An admin directory stores a single static user. The user in the Admin directory always has the attribute uid to identify the user. While intended for use by the administration system an Admin directory can be used to create static users in Unison. This insert is in unison-server-core.

Class Name

com.tremolosecurity.proxy.myvd.inserts.admin.AdminInsert

uid

The user’s login name

password

The user’s password

AmazonSimpleDB

Unison can use Amazon SimpleDB to store user and group information. This allows for a cloud based solution with no storage or backup footprint. This can provide an extremely effective way to store cloud based identities without having to deploy a cloud based LDAP directory. In order to use this directory type you must have an Amazon Web Services account. This directory should be used in conjunction with the Amazon SimpleDB provisioning target.

Class Name

com.tremolosecurity.proxy.myvd.inserts.amazon.AmazonSimpleDB

accessKey

The access key generated by Amazon Web Services

secretKey

The secret key provided by Amazon Web Services

userDomain

The domain to store user information in

groupDomain

The domain to store groups in

AddGroupsFromProvisioningTarget

This insert creates a virtual attribute, which can only be viewed but not searched, that shows the groups a user is a member of from a specific provisioning target.

Class Name

com.tremolosecurity.proxy.myvd.inserts.AddGroupsFromProvisioningTarget

attributeName

The name of the attribute to create

targetName

The name of the target to search

uidAttribute

The name of the attribute used to identify the user in the provisioning target

label

A prefix to put on the group

MyTarget

targetRoleAttribute

The name of the attribute in the target that stores groups, optional

role

AuthLockoutInsert

This insert performs the same function as the compliance feature of authentication chains, but in the internal virtual directory. See the compliance section of the authentication chain for details on how to deploy.

Class Name

com.tremolosecurity.proxy.myvd.inserts.compliance.AuthLockoutInsert

maxFailedAttempts

The number of attempts before a user is locked out

maxLockoutTime

The amount of time, in milliseconds, that a user should be locked out after too many failed attempts

numFailedAttribute

Attribute that stores the number of failed login attempts

lastFailedAttribute

Attribute that stores the last failed login attempt timestamp, stored as the number of seconds since EPOCH

lastSucceedAttribute

Attribute that stores the last successful login attempt timestamp, stored as the numer of seconds since EPOCH

updateAttributesWorkflow

Name of the workflow to update directory attributes

uidAttributeName

The name of the attribute to identify the user

MongoInsert

The MongoDB insert provides search capabilities via LDAP of a mongodb database. Each collection maps to an OU. Each document MUST have two attributes:

  • unisonRdnAttributeName - The name of the attribute that stores the rdn of the directory object

  • The attribute named in unisonRdnAttributeName

While not strictly required, an objectClass attribute should also be present for the objects to work consistently with LDAP.

At this time ONLY search capabilities are implemented. For updates and modifications, see the provisioning target.

Class Name

com.tremolosecurity.mongodb.myvd.MongoInsert

url

A MongoDB connection url as defined by https://docs.mongodb.com/manual/reference/connection-string/

database

The name of the database to use

AddAttributesFromProvisioningTarget

Adds attributes directly from a provisioning target.

Class Name

com.tremolosecurity.proxy.myvd.inserts.AddAttributesFromProvisioningTarget

attributeNames

The name of the attributes to load from the target

targetName

The name of the target to search

uidAttribute

The name of the attribute used to identify the user in the provisioning target

BasicDatabase

The basic database insert provides a wrapper around MyVD’s database insert to provide more capabilities without adding additional inserts or namespaces. It can provide user object, group objects, authenticate users and map users to groups from outside of the database insert’s namespace. If user objects will be mapped, all users will be under ou=users. If group objects are mapped all groups are under ou=groups.

Class Name

com.tremolosecurity.proxy.myvd.inserts.db.BasicDatabase

driver

The class of the JDBC driver

url

The JDBC URL for accessing the database

user

The user for connecting to the database

password

The password for the database

maxCons

The maximum number of connections to the database

validationQuery

Query used to test connections when being checked out of the pool

useUsers

true if an ou=users branch should be created for this insert

user-table

The name of the table that stores the user objects

user-primaryKey

he name of the column in the user table that is the primary key

user-mapping

Comma separated list of name/value pairs where the left if the ldap name, and the right is the column name in the database. At a minimum uid is required. ie uid=login,givenname=first_name,...

supportBind

If true, the insert will support authentication using PBKDF2, meant to be used with the BasicDB target type in OpenUnison.

passwordColumn

The name of the column in the users table that holds the PBKDF2 encoded password.

useGroups

Determines if this database stores group information about the users in the db

group-table

The name of the table that stores group information

group-primaryKey

The name of the primary key of the group table

group-mapping

Comma separated list of name/value pairs where the left if the ldap name, and the right is the column name in the database. At a minimum cn and uniqueMember are required. ie uniqueMember=login,cn=name,.... If

manyToManyTable

The name of the table used to link users and groups, ignored if groups are being populated based on groupUserSearchBase

manyToManyTable-users

The name of the column in the link table that maps to the user?s primary key, ignored if groups are being populated based on groupUserSearchBase

manyToManyTable-groups

The name of the column in the link table that maps to the group?s primary key, ignored if groups are being populated based on groupUserSearchBase

groupUserSearchBase

If not supporting user objects, supplies the search base to look for the users listed as group members, searched for by the uid attribute

idleConnectionTestPeriod

(optional - default is 30) Number of seconds unchecked out connections are tested

unreturnedConnectionTimeout

(optional - defaults to 0) Number of seconds a connection can checked out before the connection is closed and returned to the pool. 0 means it will not be forced.

checkoutTimeout

(optional - defaults to 30000 milliseconds) Number of milliseconds to wait for a connection to be available.

OpenUnisonRestful

The OpenUnisonRestful insert works with the LdapOnJson filter to integrate with OpenUnison’s internal LDAP virtual directory. This is NOT a replacement for LDAP, but in certain situations can be useful. It uses the LastMile mechanism to secure the connection to the remote OpenUnison server.

Class Name

com.tremolosecurity.proxy.myvd.inserts.restful.OpenUnisonRestful

remoteBase

The remote DN to start all searches from. All DNs will be mapped from this to the local namespace

urlBase

The url of the remote server NOT including the path. ie https://remote.server.com

uriPath

The path on the remote server to use as the base path for accessing LdapOnJson. ie /ldap

callAsUserID

The id of the LastMile user

callAsUserIDAttributeName

The name of the attribute to identify the user

lastMileKeyName

The name of the LastMile key to use to encrypt the request

K8sCrdInsert

This insert will add an LDAP interface to the User objects stored as Kubernetes custom resources. In order to work, the CRDs MUST be created.

Class Name

com.tremolosecurity.proxy.myvd.inserts.restful.OpenUnisonRestful

nameSpace

The namespace to search for User objects

k8sTargetName

The name of an OpenShift (kubernetes) target

alwaysMapUIDInFilter

true if the uid attribute should always be mapped in a filter

OpenShiftInsert

The OpenShiftInsert will create an LDAP directory of OpenShift user objects. Groups are listed as attributes on objects, not as individual objects.

Class Name

com.tremolosecurity.unison.openshiftv3.myvd.OpenShiftInsert

osTargetName

The name of the openshift target to use for connecting to OpenShift

OktaInsert

The Okta insert works with the Okta provisioning target to lookup users. All attributes from the user’s profile are included, including groups in the groups attribute.

Class Name

com.tremolosecurity.unison.okta.myvd.OktaInsert

target

The name of the Okta provisioning target used to communicate with your Okta account

objectClass

The name of the object class objects returned will be assigned

users

If true, the insert will search user profile objects. If false, the insert will search group objects.

Authentication Mechanisms

FormLoginAuthMech

An HTML login form. All login forms must be stored in the apps/tremolo-admin/auth/forms directory. Forms can be static HTML or JSP pages. See auth/forms/defaultForm.jsp.

Mechanism

There are no mechanism specific configuration items.

<mechanism name="loginForm">
  <uri>/auth/formLogin</uri>
  <className>com.tremolosecurity.proxy.auth.FormLoginAuthMech</className>
  <init>
  </init>
  <params>
    <param>FORMLOGIN_JSP</param>
  </params>
</mechanism>
Chain
<chain name="formloginFilter" level="1">
  <authMech>
    <name>loginForm</name>
    <required>required</required>
    <params>
      <!-- Path to the login form -->
      <param name="FORMLOGIN_JSP" value="/auth/forms/defaultForm.jsp"/>
      <!-- Either an attribute name OR an ldap filter mapping the form parameters. If this is an ldap filter, form parameters are identified by ${parameter} -->
      <param name="uidAttr" value="(&amp;(uid=${user})(mail=*${domain}))"/>
      <!-- If true, the user is determined based on an LDAP filter rather than a simple user lookup -->
      <param name="uidIsFilter" value="true"/>
    </params>
  </authMech>
</chain>

SAML2Auth

This mechanism is used to authenticate the user using a SAML2 assertion. The HTTP-POST and HTTP-REDIRECT profiles are supported.

Mechanism

Some identity providers, such as Active Directory Federation Services, do not have a way of providing a default RelayState for IdP Initiated SSO. In such cases, a mapping from the Referer HTTP header to a default relay state may be configured on the mechanism. See the command line reference for how to import and export metadata easily.

<mechanism name="SAML2">
  <uri>/auth/SAML2Auth</uri>
  <className>com.tremolosecurity.proxy.auth.SAML2Auth</className>
  <init>
     <!-- Provides a mapping from the Referal header to a default relay state, pipe (|) seperated -->
     <param name="defaultRelayStates" value="https://idp.ent2k12.domain.com/adfs/ls/IdpInitiatedSignOn.aspx|https://sp.autoidm.com:9090/echo/echo?roles=admin&amp;myparam=value"  />
     <param name="defaultRelayStates" value="https://idp.ent2k12.domain.com:443/adfs/ls/IdpInitiatedSignOn.aspx|https://sp.autoidm.com:9090/echo/echo?roles=admin&amp;myparam=value"  />
  </init>
  <params />
</mechanism>
Chain
<chain level="1" name="adfs">
  <authMech>
    <name>SAML2</name>
    <required>required</required>
    <params>
      <!-- The assertion consumer service URL -->
      <param value="https://idp.ent2k12.domain.com/adfs/ls/"
        name="idpURL" />
      <!-- The HTTP-Redirect endpoint -->
      <param value="https://idp.ent2k12.domain.com/adfs/ls/"
      name="idpRedirURL" />

      <!-- The name of the certificate used to validate the signed response / assertion, may be listed multiple times to support multiple certificates  -->
      <param
        value="verify2012"
        name="idpSigKeyName" />

      <!-- The RDN for an account that can not be linked to a user in the virtual directory -->
      <param value="uid" name="ldapAttribute" />

      <!-- The OU of an account that can not be linked to a user in the virtual directory -->
      <param value="ADFS" name="dnOU" />

      <!-- Object Class for an account that can not be linked to a user in the virtual directory -->
      <param value="inetOrgPerson" name="defaultOC" />

      <!-- The required authentication mechanism to be used by the identity provider -->
      <param name="authCtxRef" value="urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport"/>

      <!-- The certificate used to sign all outbound requests (AuthnRequest and LogoutRequest) -->
      <param name="spSigKey" value="spsig" />

      <!-- The SingleLogout URL of the Identity Provider -->
      <param value="https://idp.ent2k12.domain.com/adfs/ls/"
        name="idpRedirLogoutURL" />

      <!-- The final URL users are sent to after login is complete -->
      <param name="logoutURL" value="https://sp.autoidm.com:9090/logout" />

      <!-- The algorithm used to sign AuthnRequests and LogoutRequests -->
      <param name="sigAlg" value="RSA-SHA256" />

      <!-- The entityID of the remote identity provider -->
      <param name="entityID" value="https://idp.ent2k12.domain.com/adfs/services/trust" />

      <!-- True if outbound requests should be signed -->
      <param name="signAuthnReq" value="true"/>

      <!-- Determine if inbound assertions must be signed -->
      <param name="assertionsSigned" value="true" />

      <!-- Determine if inbound SAMLResponse must be signed -->
      <param name="responsesSigned" value="false" />

      <!-- If set to true, requires inbound assertions to be encrypted -->
      <param name="assertionEncrypted" value="false" />

      <!-- If assertionEncrypted is set to true, names the key used to decrypt the assertion -->
      <param name="spEncKey" value="" />

      <!-- If set to true, will force outbound requests to secure connections -->
      <param name="forceToSSL" value="false" />

      <!-- If set, provides a page notifying the user that they will be redirected to another site for authentication -->
      <param name="jumpPage" value="/auth/forms/jump.jsp" />

      <!-- If true, OpenUnison will not attempt to find the user in the virtual directory, usually used with just-in-time provisioning -->
      <param name="dontLinkToLDAP" value="false" />

      <!-- Set the url to redirect to to trigger a logout when receiving a LogoutRequest -->
      <param name="triggerLogoutURL" value="https://www.myrp.com/logout" />
    </params>
  </authMech>
</chain>

AnonAuth

Anonymous authentication is used for scenarios when user authentication is not needed.

<mechanism name="anonymous">
  <uri>/auth/anon</uri>
  <className>com.tremolosecurity.proxy.auth.AnonAuth</className>
  <init>
    <!-- The RDN of unauthenticated users -->
    <param name="userName" value="uid=Anonymous"/>

    <!-- Any number of attributes can be added to the anonymous user -->
    <param name="role" value="Users" />
  </init>
  <params>

  </params>
</mechanism>
Chain
<!-- An anonymous authentication chain MUST be level 0 -->
<chain name="anon" level="0">
  <authMech>
    <name>anonymous</name>
    <required>required</required>
    <params></params>
  </authMech>
</chain>

BasicAuth

HTTP Basic Authentication

Mechanism
<mechanism name="basicAuth">
  <uri>/auth/basic</uri>
  <className>com.tremolosecurity.proxy.auth.BasicAuth</className>
  <init>
  </init>
  <params>
    <param>realmName</param>
  </params>
</mechanism>
Chain
<chain name="basicLogin" level="1">
  <authMech>
    <name>basicAuth</name>
    <required>required</required>
    <params>
      <!-- The name of the realm to present to the user -->
      <param name="realmName" value="Tremolo Realm"/>
      <!-- The name of the attribute to use when looking for the user -->
      <param name="uidAttr" value="uid"/>
    </params>
  </authMech>
</chain>

IWAAuth

IWA, or Integrated Windows Authentication, allows a user to authenticate using their current windows Kerberos token. For IWA to work the user MUST be logged into a desktop that is a member of one of the domains configured on this mechanism. NTLM is NOT supported.

Mechanism
<mechanism name="IWA">
  <uri>/auth/iwa</uri>
  <className>com.tremolosecurity.proxy.auth.IWAAuth</className>
  <init>
    <!-- The fully qualified domain name of the domain -->
    <param name="domain" value="ENTERPRISE.DOMAIN.COM"/>

    <!-- The hostname or IP of the KDC -->
    <param name="ENTERPRISE.DOMAIN.COM.kdc" value="kdc.tremolo.lan"/>

    <!-- The service account to use -->
    <param name="ENTERPRISE.DOMAIN.COM.userName" value="tremoloSvc@ENTERPRISE.DOMAIN.COM"/>

    <!-- The service account password -->
    <param name="ENTERPRISE.DOMAIN.COM.password" value="$ecret"/>
  </init>
  <params>
    <param>realmName</param>
  </params>
</mechanism>
Chain
<chain name="iwa" level="1" >
  <authMech>
    <name>IWA</name>
    <required>sufficient</required>
    <params></params>
  </authMech>
  <!-- Fallback to form if IWA isn't available -->
  <authMech>
    <name>loginForm</name>
    <required>sufficient</required>
    <params>
      <param name="FORMLOGIN_JSP" value="/auth/forms/defaultForm.jsp"/>
    </params>
  </authMech>
</chain>

CertAuth

This mechanism supports authentication using SSL certificates. If the certificate can be associated with a user in the directory it will be, otherwise a user object is created. Note that in order for sslCert mechanisms to work certificate authentication must either be optional or required on the container.

Mechanism

Certificate Revocation Lists are configured on the mechanism. There are three types of CRLs: file based, LDAP and OCSP.

<mechanism name="sslCert">
  <uri>/auth/ssl</uri>
  <className>com.tremolosecurity.proxy.auth.CertAuth</className>
  <init>
    <!-- Comma seperated list of CRLs to check -->
    <param name="crl.names" value="openssl,ldapcrl,ocsp"/>

    <!-- CRLs are loaded once a minute -->
    <param name="crl.openssl.type" value="com.tremolosecurity.proxy.auth.ssl.FileCRL"/>
    <!-- Path to the CRL file -->
    <param name="crl.openssl.path" value="crl.pem"/>

    <!-- For checking a CRL stored in an LDAP object -->
    <param name="crl.ldapcrl.type" value="com.tremolosecurity.proxy.auth.ssl.LdapCRL"/>
    <param name="crl.ldapcrl.path" value="ldap://127.0.0.1:10983/cn=signing,ou=internal,dc=domain,dc=com"/>

    <!-- For checking against OCSP -->
    <param name="crl.ocsp.type" value="com.tremolosecurity.proxy.auth.ssl.OCSP"/>
    <param name="crl.ocsp.path" value="http://127.0.0.1:2389"/>

    <!-- Extracts can be used to get attributes out of a certificate besides the subject dn and subject alternative names -->
    <!-- Must implement com.tremolosecurity.proxy.auth.CertificateExtractSubjectAttribute, may be listed multiple times -->
    <param name="extracts" value="" />
  </init>
  <params></params>
</mechanism>
Chain

When authenticating a user using certificates the chain configuration specifies how to identify a user and link them to a user in the directory. If a user can?t be linked in the directory then a user object is created based on the components of the DN.

When a certificate has subject alternative names they are added as potential components or attributes. These attribute names are:

  1. otherName

  2. email

  3. dNSName

  4. x400Address

  5. directoryName

  6. ediPartyName

  7. uniformResourceIdentifier

  8. iPAddress

  9. registeredID

Any of these attributes are available to the matching filter for directory lookups or in the DN of an unmatched entry.

<chain name="sslClientAuth" level="1">
  <authMech>
    <name>sslCert</name>
    <required>required</required>
    <params>
      <!-- Either an attribute name OR an ldap filter mapping the certificate dn components. If this is an ldap filter, dn components are identified by ${component} -->
      <param name="uidAttr" value="(uid=${CN})" />
      <!-- If the UID Attribute is a filter or just an attribute name   -->
      <param name="uidIsFilter" value="true"/>
      <!-- A list of attributes in the certificate subject, or subject alternative names, that will be the RDN of an unmatched entry. -->
      <param name="rdnAttribute" value="CN" />
      <!-- The object class to use for objects created because the user doesn?t exist in the directory -->
      <param name="defaultOC" value="inetOrgPerson" />

      <!-- The ou component of the DN to use for users not matched. For instance if SSL is specified the user?s dn would be uid=user,ou=SSL,o=Tremolo -->
      <param name="dnLabel" value="ou=CertAuth"/>

      <!-- List of DN?s of trusted certificates that the chain will accept -->
      <param name="issuer" value="OU=Dev, O=Tremolo Security Inc., C=US, ST=Virginia, CN=Root CA"/>
    </params>
  </authMech>
</chain>

UserOnlyAuthMech

An HTML login form that ONLY collects a username. This mechanism is convinient when using a custom authentication scheme or authentication system that doesn’t have a password (like SMS). All login forms must be stored in the auth/forms directory. Forms can be static HTML or JSP pages. See auth/forms/userOnlyLogin.jsp.

Mechanism
<mechanism name="userOnlyForm">
  <uri>/auth/userOnlyLogin</uri>
  <className>com.tremolosecurity.proxy.auth.UserOnlyAuthMech</className>
  <init>
  </init>
  <params>
    <param>FORMLOGIN_JSP</param>
  </params>
</mechanism>
Chain
<chain name="userOnlyLoginChain" level="1">
  <authMech>
    <name>userOnlyForm</name>
    <required>required</required>
    <params>
      <!--   The URI for the jsp page used to log the user in -->
      <param name="loginJSP" value="/auth/forms/userOnlyLogin.jsp
      <!-- Either an attribute name OR an ldap filter mapping the form parameters. If this is an ldap filter, form parameters are identified by ${parameter} -->
      <param name="uidAttr" value="uid"/>
      <!-- If true, the user is determined based on an LDAP filter rather than a simple user lookup   -->
      <param name="uidIsFilter" value="false"/>
      <!-- The URI for the jsp page used when a user can't be found -->
      <param name="noUserJSP" value="/auth/forms/noUser.jsp"/>
    </params>
  </authMech>
</chain>

AcknowledgeAuthMech

The Banner Acknowledge mechanism provides a way to make a user acknowledge a set of policies prior to logging in. Adding this mechanism to a chain to record the acknowledgement in the authorization logs. The stock acknowledgement form is in /auth/forms/acknowledge.jsp.

Mechanism
<mechanism name="acknowledge">
  <uri>/auth/acknowledge</uri>
  <className>com.tremolosecurity.proxy.auth.AcknowledgeAuthMech</className>
  <init>
  </init>
  <params>
  </params>
</mechanism>
Chain
<chain name="login" level="1">
  <!-- First authenticate the user's identity -->
  <authMech>
    <name>loginForm</name>
    <required>required</required>
    <params>
      <param name="FORMLOGIN_JSP" value="/auth/forms/defaultForm.jsp"/>
    </params>
  </authMech>
  <authMech>
    <name>acknowledge</name>
    <required>required</required>
    <params>
      <!-- The URI for the jsp page to host the banner and request acknowledgement   -->
      <param name="loginJSP" value="/auth/forms/acknowledge.jsp"/>
      <!-- The text of the banner, may be HTML  -->
      <param name="banner" value="I acknowledge that I shall do no harm"/>
    </params>
  </authMech>
</chain>

TwilioSMS

This mechanism allows for single use password to be used and sent over SMS to a user?s mobile phone via Twilio. Note, a Twilio account is required to use this mechanism.

Mechanism
<mechanism name="smsAuth">
  <uri>/auth/smsAuth</uri>
  <className>com.tremolosecurity.proxy.auth.otpsms.TwilioSMS</className>
  <init></init>
  <params></params>
</mechanism>
Chain
<chain name="2fasms" level="2">
  <!-- First collect the user's identity -->
  <authMech>
    <name>loginForm</name>
    <required>required</required>
    <params>
      <param name="FORMLOGIN_JSP" value="/auth/forms/defaultForm.jsp"/>
    </params>
  </authMech>
  <authMech>
    <name>smsAuth</name>
    <required>required</required>
    <params>
      <!-- Twilio Account SID   -->
      <param name="accountSID" value=""/>
      <!-- Twilio Account token -->
      <param name="authToken" value="" />
      <!--   Twilio Source Phone Number -->
      <param name="fromNumber" value="1234567890" />
      <!-- The attribute that stores the user?s phone number   -->
      <param name="toAttrName" value="mobile"/>
      <!-- URI for the form to collect the login key  -->
      <param name="redirectForm" value="/auth/forms/smsKey.jsp"/>=
      <!-- The message to be sent to the user. ?${key}? is used to represent the single use password.  -->
      <param name="message" value="${key}"/>
      <!-- The length of the single use password  -->
      <param name="keyLength" value="10"/>
      <!-- Checked if the single use password will have upper case letters   -->
      <param name="useLowerCase" value="true"/>
      <!-- Checked if the single use password will have lower case letters   -->
      <param name="useUpperCase" value="false"/>
      <!-- Checked if the single use password will have numbers   -->
      <param name="useNumbers" value="true"/>
    </params>
  </authMech>
</chain>
SecretQuestionAuth

This mechanism allows for secret or ?golden? to be used as a password. The answers are stored in JSON as an attribute on the user?s object and are hashed. All questions and answers are encrypted. NOTE: this mechanism is designed to be used with the com.tremolosecurity.proxy.auth.secret.CreateSecretQuestionsTask provisioning task.

Mechanism
<mechanism name="SecretQuestions">
  <uri>/auth/secretQuestions</uri>
  <className>com.tremolosecurity.proxy.auth.secret.SecretQuestionAuth</className>
  <init>
    <!-- List of questions -->
    <param name="questionList" value="What is your favorite team?"/>
    <param name="questionList" value="What is your favorite color?"/>
    <param name="questionList" value="What is your favorite pet?"/>
    <param name="questionList" value="What is your favorite town?"/>
    <param name="questionList" value="What is your favorite candy?"/>
  </init>
  <params>
  </params>
</mechanism>
Chain
<chain name="formlogin" level="1">
  <!-- First collect the user's identity, could also be the UserOnlyAuthMech -->
  <authMech>
    <name>loginForm</name>
    <required>required</required>
    <params>
      <param name="FORMLOGIN_JSP" value="/auth/forms/defaultForm.jsp"/>
    </params>
  </authMech>
  <authMech>
    <name>SecretQuestions</name>
    <required>required</required>
    <params>
      <!-- The attribute that stores the base64 encoded json   -->
      <param name="questionAttr" value="jpegPhoto"/>
      <!-- The URI of the secret question answer form  -->
      <param name="loginForm" value="/auth/forms/secretQuestions.jsp"/>
      <!-- One way hash to use -->
      <param name="alg" value="SHA-512"/>
      <!-- Used to randomize the hash  -->
      <param name="salt" value="I love tremolo security!"/>
    </params>
  </authMech>
</chain>

LoginService

The Login Service mechanism provides a way to give users a choice in how they login. This is useful in situations where the user could have multiple tokens and different levels of authentication. For instance, in a scenario where a user might be able to use 2-factor authentication when they have a token or a single factor when they don’t.

The way the login service works is it redirects the user to another Application URL for authentication. Once the chain for that application URL is completed the user is re-directed back to the original request (with post preservation). This provides the advantage of providing the authentication level of the desired chain. Each application URL should be configured with the com.tremolosecurity.prelude.filters.CompleteLogin filter. This will complete the login process.

Note
If only one option is configured in the service it will NOT prompt the user to choose.
Mechanism
<mechanism name="LoginService">
  <uri>/auth/loginService</uri>
  <className>com.tremolosecurity.proxy.auth.LoginService</className>
  <init>
  </init>
  <params></params>
</mechanism>
Chain

This mechanism should be the ONLY mechanism on a chain. In addition, the chain should be set at the lowest level of the other authentication chains involved. For instance if a chain on /login/ssl is set at 40 and another chain on /login/form is set at 20 then this chain should be 20.

For each login choice, the steps are

  • Create an authentication chain

  • Create an Application URL

  • Associate the URL with the chain

  • On the Application URL, add the com.tremolosecurity.prelude.filters.CompleteLogin filter

  • Add the URI for this URL to the chain configuration for the login service

<chain level="1" name="LoginService">
    <authMech>
      <name>LoginService</name>
      <required>required</required>
      <params>
        <!-- chains parameters point to the login endpoint for a choice and the label for the user's form -->
        <param name="chains" value="SAML2 Login=/login/saml2" />
        <param name="chains" value="Username and Password=/login/form" />
        <!-- The URI of the login method select page   -->
        <param name="serviceUrl" value="/auth/forms/chooseLogin.jsp" />
        <!-- The name of the cookie to store the user's decision to keep the user's decision to save the choice.   -->
        <param name="cookieName" value="tremoloLoginChoice"/>
        <!-- The number of days the cookie that determines if the user wants to remember their login choice will be valid.   -->
        <param name="cookieDays" value="30" />
      </params>
    </authMech>
  </chain>
</authChains>

OAuth2BearerLastMile

This mechanism allows for the use of a Unison Last Mile token to be used as a bearer token for OAuth2. The token must have one attribute named dn that maps to the user’s DN in Unison’s virtual directory.

Mechanism
<mechanism name="oauth2">
    <uri>/auth/oath2</uri>
    <className>com.tremolosecurity.proxy.auth.oauth2.OAuth2BearerLastMile</className>
    <init></init>
    <params></params>
</mechanism>
Chain
<chain name="oauth2" level="5">
   <authMech>
       <name>oauth2</name>
       <required>required</required>
       <params>
           <!-- The realm for the bearer tokern -->
           <param name="realm" value="MyEntWS" />
           <!-- The scope of the token -->
           <param name="scope" value="Mobile" />
           <!-- The encryption key used to decrypt the token -->
           <param name="encKeyAlias" value="sessionkey" />
           <!-- Determines if the user is found based on a user attribute or the user's DN -->
           <param name="lookupByAttribute" value="true" />
           <!-- If looking up by attribute, which attribute to search? -->
           <param name="lookupAttributeName" value="uid" />
           <!-- If true, Unison will look at the URI instead of the scope to verify the LastMile -->
           <param name="useURIForLastMile" value="true" />
       </params>
   </authMech>
</chain>

OAuth2JWT

This mechanism will allow a JWT to be used as an OAuth2 Bearer Token. The token’s signature is validated along with the issuer and timestamps. The mechanism can look the user up or create an object based on the claims in the token.

Mechanism
<mechanism name="oauth2jwt">
        <uri>/auth/oauth2jwt</uri>
        <className>com.tremolosecurity.proxy.auth.oauth2.OAuth2JWT</className>
        <init>

        </init>
        <params />
</mechanism>
Chain
<chain name="oauth2-linked" level="20">
  <authMech>
    <name>oauth2jwt</name>
    <required>required</required>
    <params>
      <!-- The required Issuer -->
      <param name="issuer" value="https://k8sou.tremolo.lan/auth/idp/k8sIdp"/>
      <!-- if true, openunison will pull urls and public keys from the well-known url -->
      <param name="fromWellKnown" value="true" />

      <!-- Certificate entry to use to validate the JWT's signature ONLY NEEDED if fromWellKnown is false -->
      <param name="validationKey" value="jwt-sig"/>
      <!-- If true, the user will be looked up in the virtual directory -->
      <param name="linkToDirectory" value="true"/>
      <!-- The OU in the DN to use if the user isn't found in the virtual directory -->
      <param name="noMatchOU" value="oauth2"/>
      <!-- If not linked to a user, the claim to use as the user identifier -->
      <param name="uidAttr" value="uid"/>
      <!-- LDAP filter to lookup the user, can include attributes from the JWT -->
      <param name="lookupFilter" value="(uid=${sub})"/>
      <!-- The objectClass to use if the user can't be found -->
      <param name="defaultObjectClass" value="inetOrgPerson"/>
      <!-- HTTP Realm -->
      <param name="realm" value="kubernetes" />
      <!-- HTTP Scope -->
      <param name="scope" value="auth" />
    </params>
  </authMech>
</chain>

OAuth2K8sServiceAccount

This mechanism will validate a Kubernetes service account token against the cluster it was issued from. Once validated, the token is either mapped to a user or an ephemeral user is created for the session.

Mechanism
<mechanism name="oauth2k8s">
        <uri>/auth/oauth2k8s</uri>
        <className>com.tremolosecurity.proxy.auth.OAuth2K8sServiceAccount</className>
        <init>

        </init>
        <params />
</mechanism>
Chain
<chain name="oauth2-linked" level="20">
  <authMech>
    <name>oauth2k8s</name>
    <required>required</required>
    <params>
      <!-- The name of the Kubernetes target -->
      <param name=""k8sTarget"" value="k8s"/>
      <!-- If true, the user will be looked up in the virtual directory -->
      <param name="linkToDirectory" value="true"/>
      <!-- The OU in the DN to use if the user isn't found in the virtual directory -->
      <param name="noMatchOU" value="oauth2"/>
      <!-- If not linked to a user, the claim to use as the user identifier -->
      <param name="uidAttr" value="uid"/>
      <!-- LDAP filter to lookup the user, can include attributes from the JWT -->
      <param name="lookupFilter" value="(uid=${sub})"/>
      <!-- The objectClass to use if the user can't be found -->
      <param name="defaultObjectClass" value="inetOrgPerson"/>
      <!-- HTTP Realm -->
      <param name="realm" value="kubernetes" />
      <!-- HTTP Scope -->
      <param name="scope" value="auth" />
    </params>
  </authMech>
</chain>

JITAuthMech

Mechanism
<mechanism name="jit">
    <uri>/auth/jit</uri>
    <className>com.tremolosecurity.provisioning.auth.JITAuthMech</className>
    <init></init>
    <params></params>
</mechanism>
Chain
<chain name="jit" level="5">
   <!-- first run an authentication mechanism, such as SAML2, to validate the user and retrieve attributes -->
   <authMech>
       <name>jit</name>
       <required>required</required>
       <params>
           <!-- The name of the attribute that will be used as the user identifier-->
           <param name="nameAttr" value="uid" />
           <!-- The name of the workflow -->
           <param name="workflowName" value="JIT-Saml2" />
       </params>
   </authMech>
</chain>

PersistentCookie

he persistent cookie mechanism is used in situations where a heavy gui client (such as Office or Windows Explorer) uses http calls to do work but is unable to handle redirects or form based authentication. For instance when integrating with a webdav system that is protected by Unison. Using this mechanism, a user can be authenticated in a web browser but use Explorer or Office using a persistent cookie. This cookie is encrypted and has a certain lifespan beyond the life of the user’s Unison session.

To enhance security this mechanism uses three levels of security:

AES-256 Encryption

The cookie is encrypted using industry standard AES with 256 bit keys

Client IP Address

The user’s IP address is stored in the cookie, if the IP of the source of a request doesn’t match this value the cookie is rejected

Optional - SSL Session ID

The session id for the current SSL session can be stored as an extra layer of validation

When using this mechanism, the com.tremolosecurity.proxy.auth.persistentCookie.PersistentCookieResult custom result must be a cookie result on a Result Group that is on the Authentication Success result of the application configured with this mechanism.

Finally, when using with internet explorer the site that will generate and use this cookie MUST be "Trusted".

Mechanism
<mechanism name="persistentCookie">
    <uri>/auth/cookie</uri>
    <className>com.tremolosecurity.proxy.auth.persistentCookie.PersistentCookie</className>
    <init></init>
    <params></params>
</mechanism>
Chain
<chain name="saml2Test" level="1">
    <!-- Persistent Cookie should be first and marked as sufficient -->
    <authMech>
        <name>persistentCookie</name>
        <required>sufficient</required>
        <params>
            <!-- The name of the cookie to generate. Scoping information is taken from the application's cookie configuration.  -->
            <param name="cookieName" value="login" />
            <!-- The number of milliseconds before this cookie needs to be re-generated. Defaults to 4 hours.   -->
            <param name="millisToLive" value="10000" />
            <!-- The name of the Last Mile Key from the Certificate Management screen to use to encrypt the cookie   -->
            <param name="keyAlias" value="lastmile" />
            <!-- If true, the user's ssl session id is included in the validation process.-->
            <param name="useSSLSessionID" value="true" />
        </params>
    </authMech>
  <authMech>
  <name>saml2Test</name>
  <required>sufficient</required>
  <params>
    <param name="idpURL" value="http://shib2x.tremolo.lan/idp/profile/SAML2/POST/SSO" />
    <param name="idpRedirURL" value="http://shib2x.tremolo.lan/idp/profile/SAML2/Redirect/SSO" />
    <param name="idpSigKeyName" value="shib-idp-sig"/>
    <param name="ldapAttribute" value="uid"/>
    <param name="dnOU" value="SAML2Test"/>
    <param name="defaultOC" value="inetOrgPerson"/>
    <param name="authCtxRef" value="urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport"/>
  </params>
</authMech>

OTPAuth

This mechanism provides a one-time-password using the OATH time based protocol (sometimes referred to as Google Authenticator). This mechanism needs to be paired with a workflow that contains the com.tremolosecurity.provisioning.customTasks.CreateOTPKey custom task for setting the user’s encrypted key.

Mechanism
<mechanism name="totp">
  <uri>/auth/totp</uri>
  <className>com.tremolosecurity.proxy.auth.otp.OTPAuth</className>
  <init>
  </init>
</mechanism>
Chain
<chain name="login" level="1">
  <authMech>
    <!-- First authenticate the user -->
    <name>loginForm</name>
    <required>required</required>
    <params>
      <param name="FORMLOGIN_JSP" value="/auth/forms/defaultForm.jsp"/>
    </params>
  </authMech>
  <authMech>
    <name>totp</name>
    <required>required</required>
    <params>
      <!-- The path to the form for entering the one-time-password       -->
      <param name="formURI" value="/auth/forms/otp.jsp"   />
      <!-- The last mile key used to encrypt the user's token    -->
      <param name="keyName" value="sessionkey"   />
      <!-- The name of the attribute to retreive the user's token from, should be the same as the attributeName property of the com.tremolosecurity.provisioning.customTasks.CreateOTPKey custom task -->
      <param name="attributeName" value="givenName" />
      <!-- Number of valid 30 second windows that a token should remain usable for cases where clocks aren't exactly in-sync or to forgive codes entered over a windows switch -->
      <param name="windowSize" value="4" />
    </params>
  </authMech>
</chain>

LogUserAgentAuth

This mechanism will log the user’s browser (UserAgent header).

Mechanism
<mechanism name="logagent">
  <uri>/auth/logagent</uri>
  <className>com.tremolosecurity.proxy.auth.LogUserAgentAuth</className>
  <init>
  </init>
</mechanism>
Chain
<chain name="login" level="1">
  <authMech>
    <!-- First authenticate the user -->
    <name>loginForm</name>
    <required>required</required>
    <params>
      <param name="FORMLOGIN_JSP" value="/auth/forms/defaultForm.jsp"/>
    </params>
  </authMech>
  <authMech>
    <name>logagent</name>
    <required>required</required>
    <params>

    </params>
  </authMech>
</chain>

MappingAuthmech

This mechanism will perform a simple mapping of the user’s authenticated object. Its useful when using with SAML/OIDC before calling Just-In-Time provisioning. This way the provisioning engine can properly log all the attributes.

Mechanism
<mechanism name="map">
  <uri>/auth/map</uri>
  <className>com.tremolosecurity.proxy.auth.MappingAuthmech</className>
  <init>
  </init>
</mechanism>
Chain
<chain name="login" level="1">
  <authMech>
    <!-- First authenticate the user -->
    <name>loginForm</name>
    <required>required</required>
    <params>
      <param name="FORMLOGIN_JSP" value="/auth/forms/defaultForm.jsp"/>
    </params>
  </authMech>
  <authMech>
    <name>map</name>
    <required>required</required>
    <params>
      <!-- The map parameter can be repeated multiple times with the formation "new_name=pre_mapped_name" -->
      <param name="map" value="new1=old1" />
      <param name="map" value="new2=old2" />
    </params>
  </authMech>
</chain>

PasswordReset

The PasswordReset mechanism is meant to provide a short lived token that is sent over email. The mechanism was designed to work with a password reset application, like ScaleJS Password. This mechanism requires a database. No schema needs to be loaded into the database since OpenUnison will create the schema on startup if the table doesn’t exist.

Mechanism
<tns:mechanism name="passwordReset">
        <tns:uri>/auth/passwdReset</tns:uri>
        <tns:className>com.tremolosecurity.proxy.auth.PasswordReset</tns:className>
        <tns:init>
            <!-- Database driver -->
                <tns:param name="driver" value="com.mysql.jdbc.Driver"/>
                <!-- JDBC URL -->
                <tns:param name="url" value="jdbc:mysql://dbs.tremolo.lan/myvddata"/>
                <!-- DB User -->
                <tns:param name="user" value="myvd"/>
                <!-- DB Password -->
                <tns:param name="password" value="start123"/>
                <!-- Maximum number of connections -->
                <tns:param name="maxCons" value="5"/>
                <!-- Maximum number of connections not actively working -->
                <tns:param name="maxIdleCons" value="5"/>
                <!-- The URI to redirect users to after being authenticated if the user's session is over -->
                <tns:param name="passwordResetURI" value="/echo/echo?roles=admin&amp;myparam=value"/>
                <!-- The number of minutes a key is valid -->
                <tns:param name="minValidKey" value="1"/>
                <!-- SMTP Host -->
                <tns:param name="smtpHost" value="smtp.gmail.com"/>
                <!-- SMTP port -->
                <tns:param name="smtpPort" value="587"/>
                <!-- SMTP user -->
                <tns:param name="smtpUser" value="user@gmail.com"/>
                <!-- SMTP Password -->
                <tns:param name="smtpPassword" value="xxxxxxxxxxxx"/>
                <!-- Email with key subject line -->
                <tns:param name="smtpSubject" value="Password Reset"/>
                <!-- Message for the password reset, ${key} for the user's key -->
                <tns:param name="smtpMsg" value="Click to reset: http://localhost.localdomain:9090/auth/pwdRest?${key}"/>
                <!-- The email address for the "From" -->
                <tns:param name="smtpFrom" value="user@gmail.com"/>
                <!-- Set to true if using TLS -->
                <tns:param name="smtpTLS" value="true"/>
                <!-- Set to true to enable this mechanism -->
                <tns:param name="enabled" value="true"/>
                <!-- The HibernateSQL dialect -->
                <tns:param name="dialect" value="org.hibernate.dialect.MySQL5Dialect"/>
                <!-- Validation query to make sure the connection is still available -->
                <tns:param name="validationQuery" value="SELECT 1"/>

                <!-- Optional - The mapping file (.hbx.xml) to use if the default mapping doesn't work, must be in the classpath -->
            <tns:param name="hibernateConfig" value=""/>
            <!-- Optional - If true, create tables.  After initial configuration, you may want to disable this depending on the dialect. -->
            <tns:param name="hibernateCreateSchema" value="true"/>

    <!-- The name of the attribute to look users up by, defaults to "mail" -->
    <tns:param name="uidAttributeName" value="mail" />
        </tns:init>
        <tns:params></tns:params>
</tns:mechanism>
Chain
<tns:chain name="resetPassword" level="1">
        <tns:authMech>
                <tns:name>passwordReset</tns:name>
                <tns:required>required</tns:required>
                <tns:params>
                        <!-- The page to collect the user's email address -->
                        <tns:param name="emailCollectionRedir" value="/auth/forms/pwdResetEmail.jsp"/>
                        <!-- The splash screen telling the user a reset message has been sent -->
                        <tns:param name="splashRedirect" value="/auth/forms/pwdResetSplash.jsp"/>
                        <!-- Page to show the user when an email address can't be found -->
                        <tns:param name="noUserSplash" value="/auth/forms/pwdResetNoUser.jsp"/>
      <!-- Maximum number of times a request can be made with a url -->
      <tns:param name="maxChecks" value="1" />
                </tns:params>
        </tns:authMech>
</tns:chain>

OpenIDConnectionAuthMech

This authentication mechanism is designed to support an OpenID Connect Provider (OP) such as Google, LinkedIn and KeyCloack (as with others). This implementation does not implement the full standard, only enough to allow authentication against an OP.

Mechanism
<mechanism name="oidc">
        <uri>/auth/oidc</uri>
        <className>com.tremolosecurity.unison.proxy.auth.openidconnect.OpenIDConnectAuthMech
        </className>
        <init>

        </init>
        <params />
</mechanism>
Chain
<chain name="keycloak" level="1" >
  <authMech>
    <name>oidc</name>
    <required>required</required>
    <params>
      <!-- Name of the request object the bearer token should be stored in -->
      <param name="bearerTokenName" value="kcBearerToken" />
      <!-- The client_id supplied by the OP -->
      <param name="clientid" value="unison" />
      <!-- The client_secret supplied by the OP -->
      <param name="secretid" value="531c8a65-353a-40b9-93c7-1d03c243d37e" />
      <!-- What to request from the OP -->
      <param name="responseType" value="code" />
      <!-- The authentication request url of the OP -->
      <param name="idpURL" value="http://keycloak.tremolo.lan:8080/auth/realms/unison/protocol/openid-connect/auth" />
      <!-- The URL to load the token from -->
      <param name="loadTokenURL" value="http://keycloak.tremolo.lan:8080/auth/realms/unison/protocol/openid-connect/token" />
      <!-- Claims to request from the OP -->
      <param name="scope" value="openid name offline" />
      <!-- If true, the mechanism will attempt to find the user inside of OpenUnison's internal virtual directory -->
      <param name="linkToDirectory" value="true" />
      <!-- If a user isn't matched in the virtual directory, ou to be assigned to -->
      <param name="noMatchOU" value="keycloak" />
      <!-- How to lookup a user.  use ${claim_name} to include a claim from the user's id_token in the search -->
      <param name="lookupFilter" value="(uid=${preferred_username})" />
      <!-- If the user's object isn't matched in the virtual directory, the objectClass to assign -->
      <param name="defaultObjectClass" value="inetOrgPerson" />
      <!-- The name of the attribute in the id_token that identifies the user -->
      <param name="uidAttr" value="name" />
      <!-- Optional - If you want to limit which user domains to authenticate by the OP, specify that domain here -->
      <param name="hd" value="" />
      <!-- Implementation of com.tremolosecurity.unison.proxy.auth.openidconnect.sdk.LoadUserData -->
      <param name="userLookupClassName" value="com.tremolosecurity.unison.proxy.auth.openidconnect.loadUser.LoadJWTFromAccessToken" />
      <param name="jwtTokenAttributeName" value="id_token" />

    </params>
  </authMech>
</chain>

====== Loading Claims

Once the OP confirms authentication, claims must be loaded. Most OPs support the id_token attribute in the authentication response. How those claims are loaded is based on the com.tremolosecurity.unison.proxy.auth.openidconnect.sdk.LoadUserData interface. There are two implementations built into OpenUnison:

com.tremolosecurity.unison.proxy.auth.openidconnect.loadUser.LoadJWTFromAccessToken

This will load the id_token from the authentication response with the following parameters:

jwtTokenAttributeName

The name of the attribute in the authentication response that stores the id_token

id_token

com.tremolosecurity.unison.proxy.auth.openidconnect.loadUser.LoadAttributesFromWS

Calls a web service to load the claims using the bearer token from the authenticaiton process with the following parameters:

restURL

The URL to call to get the JWT

GenerateOIDCTokens

This mechanism will generate an OpenID Connect session on behalf of an authenticated user. This is useful when using OpenUnison with Kubernetes to authenticate users or proxy authentication with other identity providers and using OpenUnison’s session specific client secrets with kubectl. Use this mechanism by adding it to the end of the chain to generate the proper session.

Mechanism
<mechanism name="genoidctoken">
  <uri>/auth/oidctoken</uri>
  <className>com.tremolosecurity.proxy.auth.GenerateOIDCTokens</className>
  <init>
  </init>
  <params />
</mechanism>
Chain
<chain name="formloginFilter" level="1" root="dc=domain,dc=com">
  <authMech>
    <name>loginForm</name>
    <required>required</required>
    <params>
      <param name="FORMLOGIN_JSP" value="/auth/forms/defaultForm.jsp"/>
      <param name="uidAttr" value="uid"/>
      <param name="uidIsFilter" value="false"/>
    </params>
  </authMech>
  <authMech>
    <name>genoidctoken</name>
    <required>required</required>
    <params>
      <!-- The name of the OpenID Connect identity provider to use -->
      <param name="idpName" value="oidc" />
      <!-- The name of the trust to generate a session for -->
      <param name="trustName" value="kubernetes" />
    </params>
  </authMech>
</chain>

U2fAuth

This mechanism provides Universal Second Factor (U2F - https://fidoalliance.org/specifications/overview/) authentication. It MUST be on a chain AFTER the user is identified by another means (such as via a username and password, federation, etc). It is designed to work with the U2F Registration filter to set the user’s keys.

Mechanism
<mechanism name="u2f">
        <uri>/auth/u2f</uri>
        <className>com.tremolosecurity.unison.google.u2f.U2fAuth</className>
        <init></init>
        <params></params>
</mechanism>
Chain
<chain name="u2fChain" level="2">
        <authMech>
                <name>loginForm</name>
                <required>required</required>
                <params>
                        <param name="FORMLOGIN_JSP" value="/auth/forms/defaultForm.jsp"/>
                        <param name="uidAttr" value="uid"/>
                </params>
        </authMech>
        <authMech>
                <name>u2f</name>
                <required>required</required>
                <params>
                        <!-- The name of the attribute to retrieve the registered keys from -->
                        <param name="attribute" value="carLicense"/>
                        <!-- Encryption key for encrypting/decrypting the attribute that stores the user's tokens -->
                        <param name="encryptionKeyName" value="sessionKey"/>
                        <!-- The attribute to identify the user during the challenge and in the workflow -->
                        <param name="uidAttributeName" value="uid"/>
                        <!-- The URI of the JSP page that will challenge the U2F device during authentication -->
                        <param name="formURI" value="/auth/forms/u2fAuth.jsp"/>
                        <!-- The name of the workflow to run to update the keys attribute -->
                        <param  name="workflowName" value="registerU2f"/>

                </params>
        </authMech>
</chain>

GithubAuthMech

Provides authentication using GitHub. Prior to setup create a new OAuth2 Application in GitHub. This mechanism should be paired with the AuthorizationAuthMech mechanism to limit the teams and organizations that can authenticate the user. GitHub provides no way to limit who can authenticate to an OAuth2 Application. The user’s organizations are stored in the attribute githubOrgs and all teams are stored in the attribute githubTeams in the form org/team.

Mechanism
<mechanism name="github">
        <uri>/auth/github</uri>
        <className>com.tremolosecurity.unison.proxy.auth.github.GithubAuthMech
        </className>
        <init>

        </init>
        <params />
</mechanism>
Chain
<chain name="github" level="20" >
  <authMech>
    <name>github</name>
    <required>required</required>
    <params>
      <!-- Name in the session to store the token -->
      <param name="bearerTokenName" value="githubToken" />
      <!-- The client ID from GitHub -->
      <param name="clientid" value="xxxxxxxx" />
      <!-- The secret from GitHub -->
      <param name="secretid" value="xxxxxxxxxxxxxxxxxxxxx" />

      <!-- Scopes to request, shouldn't change -->
      <param name="scope" value="user:read user:email read:org" />

      <!-- Should the account be linked to the directory? -->
      <param name="linkToDirectory" value="true" />

      <!-- If no account exists, what OU to use for DNs -->
      <param name="noMatchOU" value="github" />
      <!-- Lookup filter for finding a user -->
      <param name="lookupFilter" value="(uid=${login})" />
      <!-- The attribute that is the unique identifier, should not change -->
      <param name="uidAttr" value="login" />
          <!-- The objectClass to use if no account can be found -->
      <param name="defaultObjectClass" value="inetOrgPerson" />
    </params>
  </authMech>
  <authMech>
        <name>az</name>
        <required>required</required>
        <params>
                <param name="rules" value="custom;github!testingopenunison/team1" />
        </params>
  </authMech>
</chain>

AuthorizationAuthMech

This mechanism provides an authorization check prior to completing a chain. This can be important when you want to limit who can authenticate when integrating an external provider without that remove provider providing a way to limit which users can be authenticated.

Mechanism
<mechanism name="az">
        <uri>/auth/az</uri>
        <className>com.tremolosecurity.proxy.auth.AuthorizationAuthMech</className>
        <init>

        </init>
        <params />
</mechanism>
Chain
<chain name="github" level="20" >
  <authMech>
    <name>github</name>
    <required>required</required>
    <params>
      <param name="bearerTokenName" value="githubToken" />
      <param name="clientid" value="xxxxxxxx" />
      <param name="secretid" value="xxxxxxxxxxxxxxxxxxxxx" />
      <param name="scope" value="user:read user:email read:org" />
      <param name="linkToDirectory" value="true" />
      <param name="noMatchOU" value="github" />
      <param name="lookupFilter" value="(uid=${login})" />
      <param name="uidAttr" value="login" />
      <param name="defaultObjectClass" value="inetOrgPerson" />
    </params>
  </authMech>
  <authMech>
        <name>az</name>
        <required>required</required>
        <params>
                <!-- Multiple rules can be listed, any need to be successful to be satisfied.  Each value is first the constraint type, then a semi-colon, then the constraint -->
                <param name="rules" value="custom;github!testingopenunison/team1" />
        </params>
  </authMech>
</chain>

DuoSecLogin

Add DUO authentication. This mechanism must be AFTER a mechanism that will load the user’s identifier.

Mechanism
<mechanism name="duo">
        <uri>/auth/duo</uri>
        <className>com.tremolosecurity.proxy.auth.DuoSecLogin</className>
        <init>

        </init>
        <params />
</mechanism>
Chain
 <authMech>
    <name>duo</name>
    <required>required</required>
    <params>
      <param name="duoIntegrationKey" value="#[DUO_INTEGRATION_KEY]"/>
      <param name="duoSecretKey" value="#[DUO_SECRET_KEY]"/>
      <param name="duoApiHostName" value="#[DUO_API_HOST]"/>
      <param name="duoAKey" value="#[DUO_A_KEY]"/>
      <param name="userNameAttribute" value="uid"/>
    </params>
 </authMech>

FullMappingAuthMech

Map attributes on the authentication process' attributes.

Source Type

Description

Source

Example

user

Map an attribute form the user?s directory object

Name of an attribute

givenName

static

A static value that doesn?t change

The static value

Myvalue

custom

A class that is used to determine the mapping

Class name, see the SDK for details on how to implement

com.mycompany.mapper.Mapper

composite

Static and attribute data

Mechanism
<mechanism name="map">
  <uri>/auth/map</uri>
  <className>com.tremolosecurity.proxy.auth.FullMappingAuthMech</className>
  <init>
  </init>
</mechanism>
Chain
<authMech>
  <name>map</name>
  <required>required</required>
  <params>
    <param name="map" value="uid|composite|${#[SUB_CLAIM:sub]}" />
    <param name="map" value="mail|composite|${#[EMAIL_CLAIM:email]}" />
    <param name="map" value="givenName|composite|${#[GIVEN_NAME_CLAIM:given_name]}" />
    <param name="map" value="sn|composite|${#[FAMILY_NAME_CLAIM:family_name]}" />
    <param name="map" value="displayName|composite|${#[DISPLAY_NAME_CLAIM:name]}" />
    <param name="map" value="memberOf|composite|${#[GROUPS_CLAIM:groups]}" />
  </params>
</authMech>

Filters

Unison provides the capability to make changes to each request. For an identity provider this typically means adding additional attributes to an assertion. For a reverse proxy, this generally means adding headers or executing workflows based on the user’s choices. The below filters come standard in OpenUnison.

Group2Attribute

This filter allows for an attribute to be added to an assertion if the user is a member of a particular group in your directory. This could be useful when providing service providers entitlement information. This filter can be added multiple times and if the user is a member of the specified group AND the attribute already exists the specified value is added to the attribute, it does not replace it.

<filter class="com.tremolosecurity.prelude.filters.Group2Attribute">
  <!-- The name of the attribute to create if the user is a member of this group        -->
        <param name="attributeName" value="roles"/>
  <!-- The value to be added or set if the user is a member of the specified group      -->
        <param name="attributeValue" value="admin"/>
  <!-- The full LDAP DN of the group being checked. This DN must be the mapped DN from inside of Unison. The ??? button next to this option may be used to search for the group based on it?s CN.       -->
        <param name="groupDN" value="cn=staticgroup,ou=internal,ou=GenericLDAP,o=Tremolo" />
</filter>

DNBase2Attribute

This filter allows for an attribute to be added to an assertion if the user’s DN in the virtual directory is a child of the specified DN. This could be useful when providing service providers entitlement information. This filter can be added multiple times and if the user is a member of the specified DN AND the attribute already exists the specified value is added to the attribute, it does not replace it.

<filter class="com.tremolosecurity.prelude.filters.DNBase2Attribute">
  <!-- The name of the attribute to create if the user is a member of this group        -->
        <param name="attributeName" value="roles"/>
  <!-- The value to be added or set if the user is a member of the specified group      -->
        <param name="attributeValue" value="admin"/>
  <!-- The full LDAP DN of the base being checked. This DN must be the mapped DN from inside of Unison.                 -->
        <param name="dn" value="cn=Users,ou=MyEnterprise,O=Tremolo" />
</filter>

LoginTest

This filter will echo the attributes of the currently logged in user. It’s a convenient way to test the login process without having to have an application to proxy or an identity provider configured. Configure this filter on a URL and that URL will use this filter to provide content back to the web browser. No filters configured after this filter are executed.

<filter class="com.tremolosecurity.prelude.filters.LoginTest">
  <!-- The URI of the JSP that will echo the information -->
  <param name="jspURI" value="/auth/forms/loginTest.jsp" />
  <!-- The path of the logout URI               -->
        <param name="logoutURI" value="/logout"/>
</filter>

XForward

The X-Forward headers (X-Forwarded-For, X-Forwarded-Host and X-Forwarded-Proto), are a defacto standard for supplying down-stream servers with information as a reverse proxy would see it. This filter will create these attributes for use as headers.

<filter class="com.tremolosecurity.proxy.filters.XForward">
  <!-- Determine if plain HTTP Headers or Secure Headers should be used. If checked, standard headers are created, if not checked then attributes are created with the same names that can be added as Secure Headers to the LastMile filter.           -->
        <param name="createHeaders" value="false"/>
</filter>

CreateAWSRoleAttribute

This filter is designed to be added to an identity provider for the AWS console to create an attribute that is acceptable to AWS out of human readable role names.

<filter class="com.tremolosecurity.proxy.filters.custom.CreateAWSRoleAttribute">
  <!-- The name of an attribute on the user object that contains all of the names of roles to be included in the assertion. NOTE: each role should exist in IAM or the authentication will fail. -->
        <param name="sourceAttribute" value="roleNames"/>
  <!-- The AWS account number that should be included in the role mapping.      -->
  <param name="idpName" value="1234567"/>
  <!-- The name of the identity provider in your AWS IAM configuration to bind to.      -->
  <param name="accountNumber" value="MyIdp"/>
</filter>

StopProcessing

The ?Stop Processing? filter will stop all processing, not executing any filters configured after it or sending a request to the proxied server. This filter has no configuration options

<filter class="com.tremolosecurity.prelude.filters.StopProcessing" />

ExecuteWorkflow

This insert will execute a workflow using the user loaded by the authentication process.

<filter class="com.tremolosecurity.proxy.filters.ExecuteWorkflow">
  <!-- The name of the workflow to execute       -->
        <param name="workflowName" value="jitdbwf" />
  <!-- The name of the attribute on the user object used to identify the user   -->
        <param name="uidAttributeName" value="uid" />
</filter>

UserToJSON

This filter will create a JSON object based on the user?s attributes. The class com.tremolosecurity.proxy.auth.AuthInfo is serialized into JSON into the attribute UserJSON. This attribute can then be used as a header in a result or a LastMile attribute.

<filter class="com.tremolosecurity.proxy.filters.UserToJSON">
  <!-- If set to ?true? the request is continued to be proxied. If set to false, the request completes  -->
  <param name="doProxy" value="false" />
</filter>

AzFilter

If an application is configured to not use a session, the user?s context may be set in a filter but the authorization process will not be executed. This filter will execute authorization rules and execute result groups in this scenario. There are no configuration options for this filter, as it uses the rules configured on the application.

<filter class="com.tremolosecurity.proxy.filters.AzFilter" />

RemoteBasic

This filter will authenticate users by executing a basic authentication request against a remote server using the Authorization header inbound from the browser. The filter will then set the user?s context. Its designed to work with an application?s session disabled.

<filter class="com.tremolosecurity.proxy.filters.RemoteBasic">
  <!-- The name of the realm on the remote web server the authentication is against     -->
        <param name="realmName" value="Tremolo Basic Realm"/>
  <!-- The url to use to test the authentication        -->
        <param name="url" value="http://localhost.localdomain:9090/echo/echo4"/>
</filter>

LastMile

The last mile security filter generates the token utilized to validate the request by the Last Mile system deployed on the application. This filter can be configured to add attributes, roles and other information to the last mile token. It also supplies the configuration needed for the application?s last mile configuration.

<filter class="com.tremolosecurity.proxy.filters.LastMile">
  <!-- The key used to encrypt the last mile header     -->
  <param value="lastmile-userreg" name="encKeyAlias" />
  <!-- The number of milliseconds that the last mile token is valid     -->
        <param value="1000" name="timeScew" />
  <!-- The name of the header   -->
        <param value="tremoloHeader" name="headerName" />
        <!-- Static text to put in front of the token, ie for OAuth2 bearer tokens -->
        <param value="" name="headerPrefix" />
  <!-- Each attribs lists a mapping from a user attribute to the attribute in the last mile token -->
  <param value="uid=displayName" name="attribs" />
        <param value="uid=mail" name="attribs" />
</filter>

CheckADShadowAccounts

When integrating with an AD environment that is used for both shadow accounts and real accounts this filter is used to transition from real account in an external forest to a shadow account.

<filter class="com.tremolosecurity.proxy.filters.CheckADShadowAccounts">
  <!-- The UPN suffix for AD forest storing shadow accounts      -->
  <param name="nonShadowSuffix" value="shadows.ad.local" />
  <!-- The attribute that?s used for the source of the shadow account?s UPN      -->
  <param name="upnAttributeName" value="mail" />
  <!-- Attribute to store a flag value if the account is to be a shadow account  -->
  <param name="flagAttributeName" value="description" />
  <!-- Flag value if the user will become a shadow account       -->
  <param name="flagAttributeValue" value="shadow" />
</filter>

BasicAuth

This filter is used in conjunction with an application with a disabled session. It will perform a basic authentication against the internal virtual directory.

<filter class="com.tremolosecurity.proxy.filters.BasicAuth">
  <!-- The name of the realm to be presented to the browser      -->
  <param name="realmName" value="Tremolo Basic Realm"/>
  <!-- The name of the attribute to use to lookup the user      -->
  <param name="uidAttr" value="uid"/>
</filter>

AnonAz

This filter is used in conjunction with an application with a disabled session. It will create an AuthInfo object based on an anonymous user.

<filter class="com.tremolosecurity.proxy.filters.AnonAz">
  <!-- RDN of the anonymous user -->
        <param name="userName" value="uid=Anonymous"/>
</filter>

HideCookie

This filter will remove all cookies set by the proxied applications prior to being sent to the client. The cookies are stored in an internal cookie jar in the user?s session. There are no configuration options.

<filter class="com.tremolosecurity.proxy.filters.HideCookie" />

LastMileJSON

Used inconjunction with the OAuth2 Last Mile Bearer Token authentication scheme, this filter will create an HTML page with an OAuth 2 access token inside of a div called "json".

<filter class="com.tremolosecurity.proxy.filters.LastMileJSON">
    <!-- Last Mile encryption key        -->
    <param name="encKeyAlias" value="sessionkey"/>
    <!-- The number of seconds the returned token is valid      -->
    <param name="secondsScew" value="300" />
    <!-- The number of seconds to adjust for if clocks are not synced   -->
    <param name="secondsToLive" value="100"/>
</filter>

PreAuthFilter

Some applications do not work well with a reverse proxy and require an explicit "login" step. In these scenarios the Pre-Authentication filter can be used to create a session prior to the first time a user accesses the website. This filter does a Last Mile login to the url and can optionaly generate a SAML2 assertion and perform an IdP initiated SSO. Once the login is complete the cookies from the request are added to the user’s cookie jar.

<filter class="com.tremolosecurity.proxy.filters.PreAuthFilter">
    <!-- Determines if a SAML assertion should be generated and posted to the Pre-Auth URL. If checked an Identity Provider MUST be created to supply the configuration information to generate the assertion.  -->
    <param name="postSAML" value="true"/>
    <!-- The fully qualified domain name (FQDN) and uri of the URL in Unison configured with a LastMile filter configured, only needed if postSAML is true -->
    <param name="url" value="http://mysite.company.com/myapp/login"/>
    <!-- The name of the identity provider that has the configuration information for generating the assertion  -->
    <param name="idpName" value="saml2" />
    <!-- The host name that should be in the issuer     -->
    <param name="issuerHost" value="localhost.localdomain"/>
    <!-- If the issuer is on a non-standard port, it can be specified here. This field is optional      -->
    <param name="issuerPort" value="6060"/>
    <!-- If the issuer should be https (checked) or http (not checked)  -->
    <param name="issuerSSL" value="false"/>
    <!-- If the application expects a RelayState -->
    <param name="relayState" value="/echo/echo"/>
</filter>

Groups2Attribute

This filter will create an attribute with the names of groups the user is a member of. An optional regular expression can be used to specify only a certain number of groups.

<filter class="com.tremolosecurity.prelude.filters.Groups2Attribute">
  <!-- The base in the virtual directory to begin searching for groups.  -->
  <param name="base" value="o=Tremolo" />
  <!-- Name of the attribute to create  -->
  <param name="attrName" value="roles" />
  <!-- A regular expression to filter out group memberships     -->
  <param name="pattern" value="groups-(.*)" />
  <!-- If a pattern is specified, the group from that pattern to add to the attribute   -->
  <param name="groupNum" value="1" />
</filter>

CookieFilter

This filter will stop all cookies, except those configured, from being sent to downstream applications. This can be used to stop third party cookies, attempts to spoof or cookie collisions.

<filter class="com.tremolosecurity.proxy.filters.CookieFilter">
  <!-- List of cookie names (or regular expressions) to filter  -->
        <param name="ignore" value="cookie2"/>
  <!-- If checked, the values to filter are treated as Java regular expressions. (http://docs.oracle.com/javase/7/docs/api/java/util/regex/Pattern.html)        -->
  <param name="supportRegex" value="false" />
</filter>

SetNoCacheHeaders

This filter will set response headers that will tell browsers not to cache the content.

<filter class="com.tremolosecurity.proxy.filters.SetNoCacheHeaders">
</filter>

AddHttpsToRedirect

This filter will replace http:// with https:// in the Location response header. Useful when overiding the Host header is disabled but the proxy transitions from https to http.

<filter class="com.tremolosecurity.proxy.filters.AddHttpsToRedirect">
</filter>

CheckSession

This filter will check to see if the user’s session is active, and if so how much longer the user’s session is. This filter should be configured on its own application, seperate from the application being tested and with its own sesison cookie. This way the user’s session is not reloaded when checked.

<filter class="com.tremolosecurity.proxy.filters.CheckSession">
        <!-- The name of the application who's session cookie data to check -->
    <param name="applicationName" value="ScaleJS"/>
</filter>

RemovePrefix

The RemovePrefix filter will remove the beginning of the URI and put it into an attribute that can be included in the proxyTo.

 <filter class="com.tremolosecurity.proxy.filters.RemovePrefix">
    <!-- The part of the URI to trim off -->
    <param name="prefix" value="/scale"/>
    <!-- The attribute to set with the new URI -->
    <param name="attributeName" value="trimmedURI"/>
</filter>

RetrieveIdToken

Kubernetes does not retrieve your id_token using the authorization flow, it assumes that the bearer token provided is an id_token with a short expiration date. In this filter provides a simple service that will generate the id_token for an authenticated session based on the session’s refresh_token. Configure this filter on an anonymous URL and pass a single parameter, refresh_token, to get a proper id_token:

$ kubectl --token=$(curl https://openunison.server/service/idtoken?refresh_token=....) get nodes
Note
this will not generate a new token once the current one has expired. That requires going to ScaleJS and refreshing the token screen to get a new id_token.
<filter class="com.tremolosecurity.proxy.filters.RetrieveIdToken">
  <!-- The name of the identity provider to pull the id_token from -->
  <param name="idpName" value="oidc" />
  <!-- The name of the trust to pull the id_otken from -->
  <param name="trustName" value="kubernetes" />
</filter>

CallWorkflow

Allows for a remote OpenUnison to call a workflow by calling a PUT of a workflow request. REcommended to use with an OAuth2 authentication chain.

<filter class="com.tremolosecurity.proxy.filters.CallWorkflow">
    <!-- Determines which workflows can be called, can be listed multiple times -->
    <param name="allowedWorkflow" value="updateLockout"/>
 </filter>

HeaderFilter

Removes an inbonud header from a request. Header names are NOT case sensistive

<filter class="com.tremolosecurity.proxy.filters.HeaderFilter">
  <!-- May be listed multiple times for multiple headers -->
  <param name="headerName" value="ToBeRemoved" />
</filter>

U2F Registration

This filter provides services for registering U2F keys (https://fidoalliance.org/specifications/overview/). Its designed to work with the U2fAuth authentication mechanism.

<filter class="com.tremolosecurity.unison.google.u2f.Registration">
        <!-- Encryption key for encrypting/decrypting the attribute that stores the user's tokens -->
        <param name="encryptionKeyName" value="sessionKey" />
        <!-- The name of the attribute to retrieve the registered keys from -->
        <param name="attribute" value="carLicense" />
        <!-- The URI of the JSP page that will challenge the U2F device and provide the registration response -->
        <param name="challengeURI" value="/auth/forms/u2fRegistration.jsp" />
        <!-- The name of the workflow to run to update the keys attribute -->
        <param name="workflowName" value="registerU2f" />
        <!-- The attribute to identify the user during the challenge and in the workflow -->
        <param name="uidAttributeName" value="uid" />
        <!-- The URI of the jsp page to send the user to once the regitration process is completed -->
        <param name="completedURI" value="/auth/forms/u2fRegistrationComplete.jsp" />
        <!-- If you only want to allow certain devices or manufacturers -->
        <param name="requireAttestation" value="true" />
        <!-- If requiring attestation of the device, list of certificates used to check the attestation.  May be listed multiple times. -->
        <param name="trustedCertificate" value="trusted-yubico" />
</filter>

A workflow is needed to write the key data to your data store. Below is an example:

<workflow name="registerU2f">
        <tasks>
            <customTask className="com.tremolosecurity.provisioning.customTasks.LoadGroups">
                        <param name="nameAttr" value="uid"/>
                        <param name="inverse" value="false"/>
                </customTask>
                <provision sync="true" target="ldap"/>
        </tasks>
</workflow>

LdapOnJson

The LdapOnJson filter will provide LDAP like functionality via a RESTful JSON interface. The filter provides search and bind capabilities only. Unlike LDAP, searches and binds are NOT tied together. This is a convenience for accessing OpenUnison’s internal virtual directory without using the LDAP protocol. It is not a replacement for LDAP.

<filter class="com.tremolosecurity.proxy.filters.LdapOnJson" />
Swagger Definition
swagger: '2.0'
info:
  version: "1.0.17"
  title: OpenUnison Ldap2Json

definitions:
  Attributes:
    type: object
    additionalProperties:
      type: array
      items:
        type: string
    description: Map of attribute name --> multiple values

  LdapJsonBindRequest:
    type: object
    properties:
      password:
        type: string
        description: Secret for authentication
    description: in java package com.tremolosecurity.ldapJson

  LdapJsonError:
    type: object
    description: in java package com.tremolosecurity.ldapJson
    properties:
      responseCode:
        type: integer
        description: LDAP Response code
      errorMessage:
        type: string
        description: response message

  LdapJsonEntry:
    type: object
    description: in java package com.tremolosecurity.ldapJson
    properties:
      dn:
        type: string
        description: The distinguished name of the entry
      attrs:
        $ref: '#/definitions/Attributes'

paths:
  /{base_dn}/{search_scope}:
    get:
      description: performs an LDAP Search
      parameters:
        -
          name: base_dn
          in: path
          description: Search base
          required: true
          type: string
        -
          name: search_scope
          in: path
          description: One of base, sub, one
          required: true
          type: string
        -
          name: filter
          in: query
          description: LDAP Filter
          required: true
          type: string
        -
          name: attributes
          in: query
          description: each value is an attribute to be returned
          type: string
          minimum: 0
      responses:
        200:
          description: list of entries
          schema:
            type: array
            items:
              $ref: '#/definitions/LdapJsonEntry'
        500:
          description: error
          schema:
            $ref: '#/definitions/LdapJsonError'

  /{user_dn}:
    post:
      description: performs an LDAP Bind to verify the user's credentials
      parameters:
        -
          name: user_dn
          in: path
          description: The distinguished name of the user to check
          type: string
          required: true
        -
          name: body
          in: body
          schema:
            $ref: '#/definitions/LdapJsonBindRequest'
          description: Bind request
      responses:
        200:
          description: valid credentials
          schema:
            $ref: '#/definitions/LdapJsonError'
        500:
          description: error
          schema:
            $ref: '#/definitions/LdapJsonError'

MetricsFilter

This filter will generate Prometheus metrics for OpenUnison. It includes metrics for:

  • StandardExports

  • MemoryPoolsExports

  • GarbageCollectorExports

  • ThreadExports

  • ClassLoadingExports

  • VersionInfoExports

Additionally the active_sessions metric is provided that has the number of open sessions in OpenUnison. No security is propvided for this filter, instead use OpenUnison’s built in security features to add authentication.

<filter class="com.tremolosecurity.prometheus.filter.MetricsFilter" >
  <!-- Optional - implementation of com.tremolosecurity.prometheus.sdk.LocalMetrics to add custom metrics -->
  <param name="localMetricsClassName" value="com.tremolosecurity.MyMetrics" />
</filter>

JMSPull

The JMSPull listener is designed to open access to Prometheus metrics that are not directly acessible from Prometheus. Instead of a direct connection to metrics, this filter will proxy the request to a remove OpenUnison via a common message queue and wait for a response via a seperate queue. This filter should be paired with an OpenUnison instance that has the com.tremolosecurity.prometheus.aggregate.PullListener deployed.

<filter class="com.tremolosecurity.prometheus.aggregate.JMSPull" >
  <!-- Request queue -->
  <param name="requeustQueueName" value="prometheus-request.fifo" />
  <!-- Response queue -->
  <param name="responseQueueName" value="prometheus-response.fifo" />
  <!-- Job name -->
  <param name="job" value="remote" />
  <!-- instance name -->
  <param name="instance" value="prometheus" />
  <!-- Time in milliseconds to wait for a response on the response queue -->
  <param name="maxWaitTime" value="60000" />
  <!-- Implementation of com.tremolosecurity.prometheus.sdk.AdditionalMetrics for additional metrics -->
  <param name="additionalMetricsClassName" value="com.domain.idm.ExtraChecks" />
  <!-- Additional options -->
  <param name="loginTestURL" value="#[PROMETHEUS_LOGIN_URL]" />
  <param name="loginTestUserName" value="#[PROMETHEUS_LOGIN_USERNAME]" />
  <param name="loginTestPassword" value="#[PROMETHEUS_LOGIN_PASSWORD]" />
  <param name="internal" value="false" />
</filter>

K8sInjectImpersonation

Injects impersonation headers into each request designed to be used by the Kubernetes API server. In order for these headers to be accepted the must be accompanied by a service account that has impersonation rights.

<filter class="com.tremolosecurity.proxy.filters.K8sInjectImpersonation">
  <!-- The name of the OpenShift (kubernetes) target -->
  <param name="targetName" value="k8s" />
  <!-- The username attribute from the logged in user's account -->
  <param name="userNameAttribute" value="sub" />
  <!-- If true, groups are pulled directly from an LDAP search -->
  <param name="useLdapGroups" value="false" />
  <!-- if useLdapGroups is false, the attribute to use to load groups -->
  <param name="groupAttribute" value="groups" />
</filter>

Provisioning Targets

This section details the pre-built provisioning targets that are available for Unison. In addition to these targets, custom targets may be created. Consult the Unison SDK for instructions on how to create a custom target.

All targets have a common interface for specifying mappings from Unison?s current user object and how attributes will be pushed to the target. Only mapped attributes will be utilized by a provisioning target.

Source Type

Description

Source

Example

user

Map an attribute form the user?s directory object

Name of an attribute

givenName

static

A static value that doesn?t change

The static value

Myvalue

custom

A class that is used to determine the mapping

Class name, see the SDK for details on how to implement

com.mycompany.mapper.Mapper

composite

Static and attribute data

Note that if the source attribute is TREMOLO_USER_ID then the user object?s id is used. When TREMOLO_USER_ID is the target attribute it sets user object?s id.

LDAPProvider

This target provisions identities to a generic LDAPv3 directory.

<target name="ldap2"
                                className="com.tremolosecurity.provisioning.core.providers.LDAPProvider">
        <params>
    <!-- The object class for new user objects -->
                <param name="objectClass" value="inetOrgPerson" />
    <!-- Host for the ldap server       -->
                <param name="host" value="127.0.0.1" />
    <!-- The port to connect to -->
                <param name="port" value="10983" />
    <!-- A DN for a user with administrator rights to create and update accounts        -->
                <param name="adminDN" value="cn=admin,dc=domain,dc=com" />
    <!-- Credential passwords   -->
                <param name="adminPasswd" value="manager" />
    <!-- The DN pattern for new users with user attributes in ${}       -->
                <param name="dnPattern" value="uid=${uid},ou=internal,dc=domain,dc=com" />
    <!-- The base that should be used for searching for users and groups        -->
                <param name="searchBase" value="dc=domain,dc=com" />
    <!-- The name of the attribute used to identify the user    -->
                <param name="userIDAttribute" value="uid"/>
    <!-- If set to true SSL is used for the connection  -->
                <param name="useSSL" value="false"/>
    <!-- Maximum number of connections to the directory -->
                <param name="maxCons" value="10"/>
    <!-- Maximum number of individual operations per connection -->
                <param name="threadsPerCons" value="10"/>
    <!-- Millisconds an idle connection can stay open -->
    <param name="idleTimeout" value="90000" />
    <!-- Allow for users from outside the directory to be provisioned into directory groups -->
    <param name="allowExternalUsers" value="false" />

        </params>
  <!-- User mappings -->
        <targetAttribute name="uid" sourceType="user"
                source="TREMOLO_USER_ID" />
        <targetAttribute name="sn" sourceType="user"
                source="sn" />
        <targetAttribute name="l" sourceType="user"
                source="l" />
        <targetAttribute name="cn" sourceType="user"
                source="cn" />
        <targetAttribute name="givenName"
                sourceType="user" source="givenName" />
</target>
Note
To specify an alternate dnPattern in a workflows, specify it in the request object with the key LDAPProvider.NEW_USER_DN or com.tremolosecurity.unison.provisioning.ldap.newUserDN

ADProvider

This target provisions identities to a Microsoft Active Directory. Note that unlike the Active Directory directory type, the provisioning target does NOT automatically map to an inetOrgPerson object class.

<target name="activedirectory" className="com.tremolosecurity.provisioning.core.providers.ADProvider">
        <params>
                <!-- Host for the ldap server   -->
                <param name="host" value="ad.tremolo.lan"/>
    <!-- The port to connect to -->
                <param name="port" value="636"/>
    <!-- A DN for a user with administrator rights to create and update accounts        -->
                <param name="adminDN" value="cn=Administrator,cn=Users,dc=enterprise,dc=domain,dc=com"/>
    <!-- Credential passwords   -->
                <param name="adminPasswd" value=""/>
    <!-- The DN pattern for new users with user attributes in ${}       -->
                <param name="dnPattern" value="cn=${cn},cn=Users,dc=enterprise,dc=domain,dc=com"/>
    <!-- The base that should be used for searching for users and groups        -->
                <param name="searchBase" value="dc=enterprise,dc=domain,dc=com"/>
    <!-- The name of the attribute used to identify the user    -->
                <param name="userIDAttribute" value="samAccountName"/>
    <!-- If set to true SSL is used for the connection  -->
                <param name="useSSL" value="true"/>
    <!-- Maximum number of connections to the directory -->
                <param name="maxCons" value="10"/>
    <!-- Maximum number of individual operations per connection -->
                <param name="threadsPerCons" value="10"/>
    <!-- If set to true a shadow account is created. A shadow account is just like a regular account except the password is randomly generated. -->
    <param name="createShadowAccount" value="true"/>
    <!-- Millisconds an idle connection can stay open -->
    <param name="idleTimeout" value="90000" />
    <!-- Allow for users from outside the directory to be provisioned into directory groups -->
    <param name="allowExternalUsers" value="false" />


        </params>
        <targetAttribute name="uid" sourceType="user" source="TREMOLO_USER_ID"  />
        <targetAttribute name="lastname" sourceType="user" source="sn"  />
        <targetAttribute name="givenName" sourceType="static" source="staticValue"  />
        <targetAttribute name="cn" sourceType="custom" source="com.tremolosecurity.test.provisioning.CreateCN"  />
        <targetAttribute name="login" sourceType="composite" source="${firstname}.${sn}@TEST"  />
</target>
Dynamic Group Creation

The ADProvider supports creating groups dynamically. When calling AddGroupToTarget the base parameter must be added to tell the provider where to create the new group.

AmazonSimpleDBProvider

If utilized as the basis for user data in the cloud this target can be used in a workflow to populate the database.

<target name="jitdb" className="com.tremolosecurity.provisioning.core.providers.AmazonSimpleDBProvider">
  <params>
      <!-- Domain for storing user information          -->
      <param name="userDomain" value="Users"/>
      <!-- Domain for storing group information -->
      <param name="groupDomain" value="Groups"/>
      <!-- Access Key   -->
      <param name="accessKey" value=""/>
      <!-- Secret Key   -->
      <param name="secretKey" value=""/>
      <!-- The attribute that stores the userid -->
      <param name="uidAttributeName" value="uid"/>
  </params>
  <!-- Attribute mapping -->
  <targetAttribute name="login" source="uid" sourceType="user"/>
  <targetAttribute name="last" source="sn" sourceType="user"/>
  <targetAttribute name="first" source="givenName" sourceType="user"/>
</target>

BasicDB

This target can be used to create users and update their attributes in a relational database. The target can either use a generic model or a custom model can be updated by implementing a specific interface. For instructions on how to manage a custom database, see the Unison SDK. Note that this target does NOT set password.

BasicDB Table Layout with No Groups

BasicDB Table Layout with No Groups

BasicDB Table Layout with Many-to-Many Groups

BasicDB Table Layout with Many-to-Many Groups

BasicDB Table Layout with One-to-Many Groups

BasicDB Table Layout with One-to-Many Groups

<target name="jitdb" className="com.tremolosecurity.provisioning.core.providers.BasicDB">
  <params>
      <!-- The class of the JDBC driver -->
      <param name="driver" value="com.mysql.jdbc.Driver"/>
      <!-- The JDBC URL for accessing the database      -->
      <param name="url" value="jdbc:mysql://endor.tremolo.lan/autogroups"/>
      <!-- An optional character used to escape field names in SQL      -->
      <param name="beginEscape" value="`" />
      <!-- An optional character used to escape field names in SQL      -->
      <param name="endEscape" value="`" />
      <!-- The user for connecting to the database      -->
      <param name="user" value="myvd"/>
      <!-- The password for the database        -->
      <param name="password" value=""/>
      <!-- The maximum number of connections to the database    -->
      <param name="maxCons" value="0"/>

      <!-- A query to run on a connection prior to checkout to make sure it is still active -->
      <param name="validationQuery" value="SELECT 1" />
      <!-- Number of seconds between tests of idle connections -->
      <param name="idleConnectionTestPeriod" value="30" />
      <!-- Number of seconds after which a checked out connection is returned to the pool -->
      <param name="unreturnedConnectionTimeout" value="30" />
      <!-- Number of milliseconds to wait for a connection -->
      <param name="checkoutTimeout" value="30000" />
      <!-- The name of the table that stores the user objects   -->
      <param name="userTable" value="users"/>
      <!-- The name of the column in the user table that is the primary key     -->
      <param name="userPrimaryKey" value="id"/>
      <param name="userName" value="login"/>
      <!-- Determines how to manage the relationship between users and groups:
          None ? No group information is stored
          ManyToMany ? Assumes there is a table of users, table of groups and a table that links them
          OneToMany ? Assumes there?s a table of users with a one-to-many relationship with a groups table
          Custom ? Use a custom class to update user attributes and group memberships. See the SDK for implementation details
      -->
      <param name="groupMode" value="ManyToMany"/>
      <!-- The name of the table that stores group information  -->
      <param name="groupTable" value="groups"/>
      <!-- The field start stores the name of the group -->
      <param name="groupName" value="name"/>
      <!-- The name of the column in the link table that maps to the user?s primary key -->
      <param name="groupUserKey" value="userid"/>
      <!-- The name of the table used to link users and groups  -->
      <param name="groupLinkTableName" value="usergroups"/>
      <!-- The name of the column in the link table that maps to the group?s primary key        -->
      <param name="groupGroupKey" value="groupid"/>
      <!-- The name of the primary key of the group table       -->
      <param name="groupPrimaryKey" value="id"/>
      <!-- If a custom group management is used this option is used to specify how users are looked up. Use %S to specify the fields being looked up, %I for the user?s numeric ID and %L for the user?s login  -->
      <param name="userSQL" value=""/>
      <!-- If a custom group management is used this option is used to specify how groups are looked up. Use %S to specify the fields being looked up, %I for the user?s numeric ID and %L for the user?s login -->
      <param name="groupSQL" value=""/>
      <!-- The class name for a custom provider. Must implement com.tremolosecurity.provisioning.core.providers.db.CustomDB -->
      <param name="customProvider" value=""/>
      <!-- SQL for validating the connection -->
      <param name="validationQuery" value="SELECT 1"/>
      <!-- Optional - Set to true if you want to provision passwords to the database in PBKDF2 format with a 64 byte salt -->
      <param name="supportPasswords" value="false" />
      <!-- Optional - If supportPasswords is true, the column to store the password in -->
          <param name="passwordField" value="password" />

  </params>
  <!-- Attribute mapping -->
  <targetAttribute name="login" source="uid" sourceType="user"/>
  <targetAttribute name="last" source="sn" sourceType="user"/>
  <targetAttribute name="first" source="givenName" sourceType="user"/>
</target>
Dynamic Group Creation

Groups can be created by adding the INSERT SQL statement into the unison.group.create.sql key of the attribute parameter to AddGroupToTarget custom task. The first "?" should be the group’s name. Additional parameters can be added by adding unison.group.create.param.X where X is the number starting with "2" of the additional values.

TremoloTarget

In addition to provisioning to specific targets, Unison can provision to other Unison clusters. This can be used to separate out functions, separating provisioning from access management. Note that all authentication is done via SSL. Before connecting to another Unison instance an SSL certificate must be generated and signed by the valid CA.

<target name="extUnison" className="com.tremolosecurity.provisioning.core.providers.TremoloTarget">
  <params>
      <!-- The name of the workflow used to create new users    -->
      <param name="createUsersWF" value="createUsers"/>
      <!-- The name of the workflow to delete users     -->
      <param name="deleteUserWF" value="deleteUser"/>
      <!-- The name of the workflow to set a user?s password    -->
      <param name="setUserPasswordWF" value="setPassword"/>
      <!-- The name of the workflow to synchronize a user       -->
      <param name="syncUserWF" value="syncUser"/>
      <!-- The attribute name for the user identifier   -->
      <param name="uidAttrName" value="uid"/>
      <!-- The url for the unison web service   -->
      <param name="wfUrlBase" value="https://www.tremolosecurity-test.com:9093"/>
  </params>
  <!-- Attribute mapping -->
  <targetAttribute name="login" source="uid" sourceType="user"/>
  <targetAttribute name="last" source="sn" sourceType="user"/>
  <targetAttribute name="first" source="givenName" sourceType="user"/>
</target>

SugarCRM

The SugarCRM target can be used to update contacts inside of SugarCRM. It does not, at present support the creating of users.

<target name="extUnison" className="com.tremolosecurity.provisioning.core.providers.TremoloTarget">
  <params>
      <!-- The SugarCRM web services URL                -->
      <param name="url" value="http://sugarcrm.domain.com/sugarcrm/service/v2/rest.php"/>
      <!-- Administrative username      -->
      <param name="adminUser" value="admin"/>
      <!-- The user?s password  -->
      <param name="adminPwd" value=""/>

  </params>
  <!-- Attribute mapping -->
  <targetAttribute name="login" source="uid" sourceType="user"/>
  <targetAttribute name="last" source="sn" sourceType="user"/>
  <targetAttribute name="first" source="givenName" sourceType="user"/>
</target>

SharePointGroups

This target allows for a user’s groups in SharePoint to be managed by Unison. For Just-In-Time provisioning it requires that:

  • The LastMile filter is configured on a URL with access to the usergroup.asmx web service

  • The internal host name (as it is visible to Unison) is configured as a host on this URL

  • That all users managed in sharepoint are already in Active Directory and have logged in once into SharePoint

If not being used for JIT provisioning, or when Last Mile is not yet available NTLM authentication can be used. This requires that identities be already in AD and synced into sharepoint.

Multi Site Integration

If a SharePoint site is made of multiple sites, but NOT subsites off the main site, then Unison must be configured to "know" about these sites. Each Site has its own roles and those roles can only be manipulated by accessing the webservices associated with each individual site.

<target name="extUnison" className="com.tremolosecurity.provisioning.core.providers.TremoloTarget">
  <params>
      <!-- The URL, from Unison's perspective, of the web services url          -->
      <param name="webServicesURL" value="http://sharepoint-internal.domain.com/_vti_bin/usergroup.asmx"/>
      <!-- Mechanism for authentication to SharePoint. For JIT provisioning you must use Unison Last Mile. (ntlm/lastmile)      -->
      <param name="authMode" value="lastmile"/>
      <!-- Tell Unison if all subsites are a member of the root site or are distinct sites with their own web services endpoints        -->
      <param name="multiSite" value="false"/>
      <!-- List of paths for each site. For instance "/", "/MySite" without quotes      -->
      <param name="siteURI" value="/"/>
      <param name="siteURI" value="/MySite"/>
      <!-- Full user principal name of an administrative user   -->
      <param name="adminUPN" value="admin@domain.com"/>
      <!-- Password for NTLM access     -->
      <param name="adminPassword" value=""/>
      <!-- Lastmile Key Alias -->
      <param name="keyAlias" value=""/>
      <!-- Last mile header name -->
      <param name="headerName" value=""/>
      <!-- Variance in milliseconds between the Unison clock and sharepoint clock -->
      <param name="skew" value=""/>

  </params>
  <!-- Attribute mapping -->
  <targetAttribute name="login" source="uid" sourceType="user"/>
  <targetAttribute name="last" source="sn" sourceType="user"/>
  <targetAttribute name="first" source="givenName" sourceType="user"/>
</target>

FreeIPATarget

This target is for FreeIPA / Red Hat Identity Management (http://www.freeipa.org/page/Main_Page / https://access.redhat.com/products/identity-management-and-infrastructure). The provisioning target allows for the creation of users, updating of attributes and groups as well as setting passwords. In addition, the target can generate "shadow objects" designed to work with SSO and constrained delegation where a password shouldn’t be known.

<target name="rhelent.lan" className="com.tremolosecurity.unison.freeipa.FreeIPATarget">
    <params>
        <!-- The protocol and host of the FreeIPA IPA-Web server. Do NOT include any path information -->
        <param name="url" value="https://freeipa.rhelent.lan"/>
        <!-- The user name (uid attribute) of a member of the admins group -->
        <param name="userName" value="adminx"/>
        <!-- The password of the service account used to create accounts -->
        <param name="password" value="start123"/>
        <!-- If true, when a user is created a random password is generated so that the account is active and ready for use, but not usable with a password -->
        <param name="createShadowAccounts" value="true"/>
        <!-- If true, this freeipa target will work with cross domain trusts.  The userid will need to be the user's userPrincipalName, not uid -->
        <param name="multiDomain" value="false" />
        <!-- If multiDomain is true, this will tell the target the primary domain -->
        <param name="primaryDomain" value="rhelent.lan" />
        <!-- Determines the Trust View to use with multiDomain, default to Default Trust View -->
        <param name="trustView" value="Default Trust View" />
    </params>
    <targetAttribute name="sn" source="sn" sourceType="user"/>
    <targetAttribute name="givenname" source="givenname" sourceType="user"/>
    <targetAttribute name="mail" source="mail" sourceType="user"/>
    <targetAttribute name="uid" source="uid" sourceType="user"/>
    <targetAttribute name="cn" source="cn" sourceType="user"/>
    <targetAttribute name="displayname" source="displayname" sourceType="user"/>
    <targetAttribute name="gecos" source="gecos" sourceType="user"/>
</target>

OpenShift Target

This target provides provisioning capabilities for Red Hat’s OpenShift (http://openshift.com and http://openshift.org) Platform as a Service and generic Kubernetes clusters. With this target you can create and delete users, update a user’s Full Name and update groups. In order for the target to work, you need an account with cluster administration privileges.

There are two options for this account. The first is to use a user account that is either a member of a group that has been assigned or is individually assigned to the cluster-admin role. Alternatively, an OpenShift service account can be created and assigned to the cluster-admin role. An OpenShift service account is recommended over a user account as that will work regardless of how OpenShift authenticates users (ie if OpenID Connect or SAML2 is used to authenticate users).

Create an OpenShift Service Account
$ oc new-project unison-service
$ oc project unison-service
$ cat <<EOF | oc create -n unison-service -f -
kind: ServiceAccount
apiVersion: v1
metadata:
  name: unison
EOF
$ oadm policy add-cluster-role-to-user cluster-admin system:serviceaccount:unison-service:unison
$ oc describe serviceaccount unison
Name:           unison
Namespace:      unison-service
Labels:         <none>

Image pull secrets:     unison-dockercfg-oe7gj

Mountable secrets:      unison-token-gmhdw
                        unison-token-vdbkq
                        unison-dockercfg-oe7gj

Tokens:                 unison-token-gmhdw
                        unison-token-vdbkq
                        unison-token-x5y1y

$ oc describe secret unison-token-x5y1y

Name:           unison-token-x5y1y
Namespace:      unison-service
Labels:         <none>
Annotations:    kubernetes.io/service-account.name=unison,kubernetes.io/service-account.uid=bda8c387-58ea-11e6-b7c9-525400b18ee2

Type:   kubernetes.io/service-account-token

Data
====
token:          eyJhbGciOiJS...
ca.crt:         1066 bytes
namespace:      14 bytes

Use the token above in the token option below.

<target name="openshift" className="com.tremolosecurity.unison.openshiftv3.OpenShiftTarget">
    <params>
        <!-- The protocol and host and port of the OpenShift api web server. Do NOT include any path information, if running inside a pod leave this field empty and OpenUnison will determine the url based on environment variables -->
        <param name="url" value="https://openshift.tremolo.lan:8443"/>
        <!-- Set to true if using an OpenShift service account instead of a user account -->
        <param name="useToken" value="false" />
        <!-- IF useToken is true, set this to the token.  See "Creating a Service Account".  If running inside of a pod, leave this field empty and OpenUnison will use the pod's service account -->

        <!-- Multiple token types can be supported

            static - set via the token parameter
            legacy - The default, will load a Secret mounted for a ServiceAccount
            tokenapi - Loads a token mounted via the TokenRequest API, and reloads as the token is updated
            oidc - Assumes that the remote connection is based on the OpenUnison management gateway

        -->
        <param name="tokenType" value="legacy" />

        <!-- if tokenType is tokenapi, the path to the token -->
        <param name="tokenPath" value="/var/run/secrets/kubernetes.io/serviceaccount/token" />

        <!-- if tokenType is oidc -->
        <!-- the name of the idp in openunison to generate the JWT -->
        <param name="oidcIdp" value="remotek8s" />
        <!-- the name of the sub for the JWT -->
        <param name="oidcSub" value="openunison" />
        <!-- the audience for the JWT -->
        <param name="oidcAudience" value="kubernetes" />

        <param name="token" value="eyJhbGciOiJSUz..." />

        <!-- can be used to specify the certificate used for trusting the remote cluster, base64 encoded PEM file -->
        <param name="certificate" value="LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUQvekNDQXVlZ0F3SUJBZ0lHQVhaRGNTN0NNQTBHQ1NxR1NJYjNEUUVCQ3dVQU1JR1ZNU293S0FZRFZRUUQKRENGck9ITmhjR2t5TG1Gd2NITXVNVGt5TGpFMk9DNHlMakUwT0M1dWFYQXVhVzh4RXpBUkJnTlZCQXNNQ2t0MQpZbVZ5Ym1WMFpYTXhEakFNQmdOVkJBb01CVTE1VDNKbk1STXdFUVlEVlFRSERBcE5lU0JEYkhWemRHVnlNUmt3CkZ3WURWUVFJREJCVGRHRjBaU0J2WmlCRGJIVnpkR1Z5TVJJd0VBWURWUVFHRXdsTmVVTnZkVzUwY25rd0hoY04KTWpBeE1qQTRNVGMwTURVNVdoY05NakV4TWpBNE1UYzBNRFU1V2pDQmxURXFNQ2dHQTFVRUF3d2hhemh6WVhCcApNaTVoY0hCekxqRTVNaTR4TmpndU1pNHhORGd1Ym1sd0xtbHZNUk13RVFZRFZRUUxEQXBMZFdKbGNtNWxkR1Z6Ck1RNHdEQVlEVlFRS0RBVk5lVTl5WnpFVE1CRUdBMVVFQnd3S1RYa2dRMngxYzNSbGNqRVpNQmNHQTFVRUNBd1EKVTNSaGRHVWdiMllnUTJ4MWMzUmxjakVTTUJBR0ExVUVCaE1KVFhsRGIzVnVkSEo1TUlJQklqQU5CZ2txaGtpRwo5dzBCQVFFRkFBT0NBUThBTUlJQkNnS0NBUUVBMTZvTHlvRHcrcHVId1hmTHNOSUFVMUMzYjJVdWpTWGdzdDNmCnZrQU50aTQyWEV2K3ZaMU9BV1oyTzBQL3dXa2NMb0NQcVBWdUZsRGx4cjNEdTBIWnkzekxUU2Zjem9IYURGczYKdk54VGpuM3poQi9XelpkbEkyeVl1K0VSY0NkTGFBOGYzMmFzUjR5YUNkV1Nvc29TVEE2NkhDVm95NFAvcm90aApyU0JSV00rdTFjMnVHYzBocDVSNytBRDY2U1ljUXNLYUJJQ1FWamd4YmFiRW1zaTFzV1M0dFcwRGlWZzUyMTNjCmNPNWgxMmpPN1A0cjd4dHd5ZkNQbGwrYzFEMnhsa2xSSUNGUEZyZHkzL3d3UENwSCtrNU9hQ3hNY1MyZ0lHUFcKdGtFUElDR2IrNmY0bkFsMktueGlCQTJKR3FLVnMrZE9OMzRBRzAvT1JEZ2txU214bXdJREFRQUJvMU13VVRCUApCZ05WSFJFRVNEQkdnaUZyT0hOaGNHa3lMbUZ3Y0hNdU1Ua3lMakUyT0M0eUxqRTBPQzV1YVhBdWFXK0NJV3M0CmMyRndhVEl1WVhCd2N5NHhPVEl1TVRZNExqSXVNVFE0TG01cGNDNXBiekFOQmdrcWhraUc5dzBCQVFzRkFBT0MKQVFFQWFxc09DRFNwZWtONUp5dExleDFoNE5XQkpPblYwL2hDTnpNeWZvbkVmYUpjNDA2R09HUGFmTG8xNEd6ZApsbjVJZVVHQlJZL1VhMTdmUHpxKzhYQnVOMFEyTThNNWVNbEZMQ1lubXN1dG5TbHEyN0VsV05rQk5tbkZXZUIxCkM5bS9Sa1BoNG54THphYld4cUFtY3Bxb3JZWjNZdmFKU3lBSThqM2llbXBScUMyWTNEWmxqek1DSVFSNlk1bjYKcGM0aCtpQzAxT2lrV1JVdG10a1Y0MjBYVWt5am94ZzFrVHk2ODI5K1MrdGZJa1VJaitZaFZOZUVrei9iR0dmYwpRempXMWtkYUJucXNzcVRmdmt5UjRWR1NYVHNHNExjakE0QlBEOEFDR0NWcGVxelJ2NTI1QXIwMCt3R0U5d1ZrCnkrRWhSVHlTRjk3eWtSZXFYWURuREtSRG9RPT0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQ==" />

        <!-- IF useToken is false, the user name (name attribute) of a user with cluster administration (cluster-admin role) -->
        <param name="userName" value="adminx"/>
        <!-- The password of the service account used to create accounts -->
        <param name="password" value="start123"/>

    </params>
    <!-- only the fullName attribute is supported -->
    <targetAttribute name="fullName" source="displayName" sourceType="user"/>

</target>

OpenStack Keystone Target

The OpenStack Keystone target provides the ability to manage users inside of OpenStack. The target will create users, add them to groups and roles for a SQL domain. For LDAP domains it will provision users into roles. This target only supports the following attributes:

  1. name

  2. email

  3. description

  4. enabled

  5. roles

The roles attribute is made up of JSON values that express the role in Keystone tearms. The JSON looks like:

{
  name : "name_of_role",
  scope : "project",
  domain : "mydomain",
  project : "myproject"
 }

If scope is domain, then the project attribute is ignored. The id for all users in this target is the name attribute.

 <target name="openstack" className="com.tremolosecurity.unison.openstack.KeystoneProvisioningTarget">
    <params>
        <!-- The URL for Keystone's V3 base -->
        <param name="url" value="http://rdo.rheldemo.lan:5000/v3"/>
        <!-- The user name (name attribute) of a user with with global admin privileges -->
        <param name="userName" value="admin"/>
        <!-- The password of the service account used to create accounts -->
        <param name="password" value="start123"/>
        <!-- The domain holding the service account used to connect to Keystone -->
        <param name="domain" value="default" />
        <!-- The admin project for OpenStack -->
        <param name="projectName" value="admin" />
        <!-- The domain holding the admin project -->
        <param name="projectDomainName" value="default" />
        <!-- The id domain holding the users to manage -->
        <param name="usersDomain" value="e9518777a4294c6d9cbe942b2f409830" />
        <!-- Set to true if the usersDomain is not a SQL domain so that you can manage role memberships for domain members -->
        <param name="rolesOnly" value="true" />
    </params>
    <!-- only attributes supported -->
    <targetAttribute name="name" source="userPrincipalName" sourceType="user"/>
    <targetAttribute name="email" source="mail" sourceType="user"/>
    <targetAttribute name="description" source="description" sourceType="user"/>
    <targetAttribute name="enabled" source="enabled" sourceType="user"/>
    <targetAttribute name="roles" source="roles" sourceType="user"/>
</target>

MongoDBTarget

The MongoDB provisioning target provides the capability to provision users and user groups in a single database. It is meant to work with the virtual directory insert. Both users and groups can be in any collection. While MongoDB has no sense of an "objectClass", this attribute is used to distinguish between users and groups. Group memberships are stored as an attribute value on a group with an identifier, NOT a distinguished name.

Creating Groups

To create a group in MongoDB make sure the following attributes are added:

  • unisonRdnAttributeName - Tells OpenUnison what the rdn will be

  • The attribute named in unisonRdnAttributeName

  • objectClass - To identify groups

<target name="mymongodb" className="com.tremolosecurity.mongodb.unison.MongoDBTarget">
    <params>
        <!-- The MongoDB connection url per https://docs.mongodb.com/manual/reference/connection-string/  -->
        <param name="url" value=""mongodb://dbs.tremolo.lan:27017"/>
        <!-- The name of the database to use -->
        <param name="database" value="unisonprov"/>
        <!-- The value of the "objectClass" attribute for users -->
        <param name="userObjectClass" value="inetOrgPerson"/>
        <!-- The rdn attribute for users -->
        <param name="userRDN" value="uid"/>
        <!-- The user identifier attribute -->
        <param name="userIdAttribute" value="uid" />
        <!-- The group identifier attribute -->
        <param name="groupIdAttribute" value="cn" />
        <!-- The group objectClass -->
        <param name="groupObjectClass" value="groupOfUniqueNames" />
        <!-- The rdn of group objects -->
        <param name="groupRDN" value="cn" />
        <!-- Group attribute that stores members -->
        <param name="groupMemberAttribute" value="uniqueMember" />
        <!-- The user attribute used as the value for group membership -->
        <param name="groupUserIdAttribute" value="uid" />
        <!-- If true, groups may point to users in the virtual directory that are NOT in MongoDB -->
        <param name="supportExternalUsers" value="true" />
        <!-- The name of the attribute to store the object's collection in -->
        <param name="collectionAttributeName" value="collection" />

    </params>
    <targetAttribute name="sn" source="sn" sourceType="user"/>
    <targetAttribute name="givenname" source="givenname" sourceType="user"/>
    <targetAttribute name="mail" source="mail" sourceType="user"/>
    <targetAttribute name="uid" source="uid" sourceType="user"/>
    <targetAttribute name="cn" source="cn" sourceType="user"/>
    <targetAttribute name="collection" source="collection" sourceType="user"/>
    <targetAttribute name="objectClass" source="objectClass" sourceType="user"/>
</target>

K8sCrdUserProvider

This target will create objects User objects in Kubernetes. Its meant to be used with K8sCrdInsert in the virtual directory. Prior to using this insert the below CRD MUST be created.

<target name="jitdb" className="com.tremolosecurity.provisioning.providers.K8sCrdUserProvider">
    <params>
        <!-- The name of the openshift provisioning target to use -->
        <param name="k8sTarget" value="k8s" />
        <!-- The namespace to create users in -->
        <param name="nameSpace" value="openunison" />
    </params>
    <targetAttribute name="first_name" source="first_name" sourceType="user"  />
    <targetAttribute name="last_name" source="last_name" sourceType="user"  />
    <targetAttribute name="email" source="email" sourceType="user"  />
    <targetAttribute name="sub" source="sub" sourceType="user"  />
    <targetAttribute name="uid" source="uid" sourceType="user"  />
</target>

Custom Resource Defenition

---
apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
metadata:
  name: users.openunison.tremolo.io
spec:
  group: openunison.tremolo.io
  versions:
    - name: v1
      served: true
      storage: true
  version: v1
  scope: Namespaced
  names:
    plural: users
    singular: user
    kind: User
    shortNames:
    - usr
  validation:
   # openAPIV3Schema is the schema for validating custom objects.
    openAPIV3Schema:
      properties:
        spec:
          properties:
            sub:
              type: string
            groups:
              type: array
              items:
                type: string
            first_name:
              type: string
            last_name:
              type: string
            email:
              type: string

Drupal8Target

The Drupal8 target will work with the json API for JSON.

<target name="drupal" className="com.tremolosecurity.unison.drupal.drupal8.provisioning.Drupal8Target">
    <params>
        <!-- The URL for your drupal site -->
        <param name="url" value="http://192.168.56.102"/>
        <!-- Administrative credentials -->
        <param name="user" value="admin"/>
        <param name="password" value="start123"/>
    </params>
    <targetAttribute name="name" source="name" sourceType="user" targetType="string"/>
    <targetAttribute name="mail" source="mail" sourceType="user" targetType="string"/>
    <targetAttribute name="first_name" source="first_name" sourceType="user" targetType="string"/>
    <targetAttribute name="last_name" source="last_name" sourceType="user" targetType="string"/>
    <targetAttribute name="status" source="true" sourceType="static" targetType="string"/>
</target>

OktaTarget

The Okta target will provision objects to your Okta service. It will provision users and group memberships.

<target name="okta" className="com.tremolosecurity.unison.okta.provisioning.OktaTarget">
        <params>
            <!-- The token from the "API" section of your Okta domain administration console -->
                <param name="token" value="sdfgdafhh3531256afag" />
                <!-- Your Okta domain -->
                <param name="domain" value="xyz.okta.com" />
        </params>
        <targetAttribute name="login" source="uid" sourceType="user" targetType="string"/>
    <targetAttribute name="email" source="mail" sourceType="user" targetType="string"/>
    <targetAttribute name="firstName" source="givenName" sourceType="user" targetType="string"/>
    <targetAttribute name="lastName" source="sn" sourceType="user" targetType="string"/>
</target>

AzureAD Target

The AzureAD target can provision and accounts and groups to AzureAD tennants via the Graph API. It can create users, assign groups and invite guest accounts. In order to use this target, you’ll need to register an application in AzureAD and generate an access secret. You’ll also need to request the following roles:

  • Group.Create

  • Group.ReadWrite.All

  • GroupMember.ReadWrite.All

  • User.Invite.All

  • User.ManageIdentities.All

  • User.Read

  • User.ReadWrite.All

<target name="azuread" className="">
        <params>
                <!-- The AzureAD Client ID -->
                <param name="clientId" value="sdfsdfsdfsd" />
                <!-- The tenant id -->
                <param name="tenantId" value="sfgsdfsfd" />
                <!-- The secret key -->
                <param name="clientSecret" value="ssdfsdfsd" />
        </params>
        <targetAttribute name="displayName" source="displayName" sourceType="user" targetType="string"/>
    <targetAttribute name="givenName" source="givenName" sourceType="user" targetType="string"/>
    <targetAttribute name="surname" source="surname" sourceType="user" targetType="string"/>
    <targetAttribute name="userPrincipalName" source="userPrincipalName" sourceType="user" targetType="string"/>
    <targetAttribute name="id" source="id" sourceType="user" targetType="string"/>
    <targetAttribute name="mail" source="mail" sourceType="user" targetType="string"/>
    <targetAttribute name="accountEnabled" source="accountEnabled" sourceType="user" targetType="boolean"/>
</target>
Request Attribute Description Example

tremolo.azuread.external

If true, the creation is an invitation of an external guest

tremolo.azuread.invitation.redirect

For invitations, the URL to redirect users to

tremolo.azuread.invitation.message

For invitations, the message to be included in the email

GitlabUserProvider

The GitlabUserProvider can provision users and groups to GitLab. It’s capable of creating both local users and "identities" for integration with SSO. It can also provision users into groups. Any attribute that is part of the User object in GitLab can be set. Use the java bean notation. When creating an "identity", make sure to add it to the request object. All group memberships are assumed to be Developer level unless otherwise mapped as shown below.

<target name="gitlab" className="com.tremolosecurity.unison.gitlab.provisioning.targets.GitlabUserProvider">
    <params>
        <!-- The URL of your GitLab service -->
        <param name="url" value="#[GITLAB_URL]"/>
        <!-- A personal access token for an administrator account -->
        <param name="token" value="#[GITLAB_TOKEN]"/>
    </params>
    <targetAttribute name="username" source="username" sourceType="user"/>
    <targetAttribute name="email" source="email" sourceType="user"/>
    <targetAttribute name="name" source="name" sourceType="user"/>
    <targetAttribute name="isAdmin" source="isAdmin" sourceType="user"/>
    <targetAttribute name="skipConfirmation" source="true" sourceType="static"/>
    <targetAttribute name="projectsLimit" source="100000" sourceType="static" />
</target>
Create Identity
GitlabFedIdentity idToCreate = new GitlabFedIdentity();
idToCreate.setExternalUid("mmosley2");
idToCreate.setProvider("openid_connect");

ArrayList<GitlabFedIdentity> idsToCreate = new ArrayList<GitlabFedIdentity>();
idsToCreate.add(idToCreate);

request.put(GitlabUserProvider.GITLAB_IDENTITIES,idsToCreate);
Map Groups
HashMap<String,Integer> groupMap = new HashMap<String,Integer>();
groupMap.put("gitlab-group-1",AccessLevel.GUEST.toValue());
groupMap.put("gitlab-group-2",AccessLevel.GUEST.toValue());

request.put(GitlabUserProvider.GITLAB_GROUP_ENTITLEMENTS,groupMap);

ArgoCDTarget

The ArgoCDTarget is primarily a place-holder for working with ArgoCD. Most operations can be handled directly by provisioning objects into the argocd namespace, but this target can be used for direct API integration. An example is provisioning new git repositories. Use a token provisioned through the CLI.

<target name="argocd" className="com.tremolosecurity.argocd.targets.ArgoCDTarget">
    <params>
        <!-- The URL for your ArgoCD service -->
        <param name="url" value="#[ARGOCD_URL]"/>
        <!-- API token to use -->
        <param name="token" value="#[ARGOCD_TOKEN]"/>
    </params>
</target>

MatterMostProvider

Onboard users into MatterMost chat platform. Delete "disables" users, not deletes them fully.

<target name="mattermost" className="com.tremolosecurity.provisioning.core.providers.MatterMostProvider">
    <params>
        <!-- url for mattermost -->
        <param name="matterMostUrl" value="https://mattermost-url.domain.com"/>
        <!-- access token for mattermost -->
        <param name="accessToken" value="XXXXXXX"/>
    </params>
    <targetAttribute name="email" source="email" sourceType="user" targetType="string"/>
    <targetAttribute name="username" source="username" sourceType="user" targetType="string"/>
    <targetAttribute name="first_name" source="first_name" sourceType="user" targetType="string"/>
    <targetAttribute name="last_name" source="last_name" sourceType="user" targetType="string"/>
    <targetAttribute name="auth_service" source="auth_service" sourceType="user" targetType="string"/>
    <targetAttribute name="auth_data" source="auth_data" sourceType="user" targetType="string"/>
</target>

Provisioning Custom Tasks

This section details the pre-built provisioning custom tasks. These tasks can be used in your deployments without change. Consult the Unison SDK for instructions on how to create a custom task.

All tasks have a common interface for specifying configuration options. Each task can take any number of name/value pairs. A single configuration option can have multiple values by listing the name/value pair for each value.

FilterGroups

This task can be used to limit the groups that are available to a target. For instance if a user could have the groups "Admin","Developer" and "User" but the target only has the groups "Admin" and "User" this task can be used to filter out "Developer". This way no "rogue" groups are presented to a target. This task should be used inside of a mapping task to make sure that other tasks are not effected.

<customTask className="com.tremolosecurity.provisioning.customTasks.FilterGroups">
  <!-- A group name that should pass through this filter, case sensitive and can be listed multiple times       -->
  <param name="name" value="User"/>
  <param name="name" value="Admins"/>
</customTask>

LoadAttributes

This task will load attributes from a user’s entry in the virtual directory. It’s useful when a workflow is only being called with a user identifier or a subset of attributes and additional attributes are needed for reporting or decision making.

<customTask className="com.tremolosecurity.provisioning.customTasks.LoadAttributes">
  <!-- An attribute name to load, case sensitive and can be listed multiple times       -->
  <param name="name" value="givenName"/>
  <param name="name" value="sn"/>
  <!-- The name of the attribute that identifies the user in the virtual directory      -->
  <param name="nameAttr" value="uid"/>
  <!-- Optional - The directory base in Unison's ldap virtual directory to begin the search at. -->
  <!-- <param name="base" value="ou=db,o=Tremolo"/> -->
</customTask>

MapGroups

The Map User Groups task will map group names from a "global" name to a target specific name. For instance if there is a generic group called "Administrator" but the target stores administrators in the group "SYS_ADMINS" this task can be used to create that mapping. It should be deployed inside of a mapping to make sure that global groups are not effected.

<customTask className="com.tremolosecurity.provisioning.customTasks.MapGroups">
  <!-- A mapping of target from source. To map Admins --> SYS_ADMIN the value should be SYS_ADMIN=Admins. This attribute can be mapped multiple times.          -->
  <param name="map" value="SYS_ADMIN=Admins"/>
</customTask>

SetPassword

This task is useful in user registration scenarios where a user’s password must be set but the email address needs to be verified. It triggers a password reset through the password reset authenticaiton mechanism. In order for this task to work, it MUST have a password reset authentication mechanism configured where the workflow is configured.

<customTask className="com.tremolosecurity.provisioning.customTasks.SetPassword">
  <!-- The name of the password reset mechanism as defined in the Auth Mechs section.   -->
  <param name="mechName" value="PasswordReset"/>
</customTask>

Attribute2Group

This task takes the values of an attribute and adds them to a user’s groups. This is useful when building generic workflows.

<customTask className="com.tremolosecurity.provisioning.customTasks.Attribute2Group">
  <!-- The name of the attribute to get the group values from. Once the values are added, the attribute is removed from the user.               -->
  <param name="attributeName" value="roles"/>
</customTask>

JITIgnoreGroups

This task will allow for a group to be ignored during a just-in-time provisioning process. If the user is a member of the named group in named target the user’s provisioning object is also given the group. This way when the synchronization occurs the group is ignored.

<customTask className="com.tremolosecurity.provisioning.customTasks.JITIgnoreGroups">
  <!-- The name of the group to ignore          -->
  <param name="groupName" value="Administrators"/>
  <!-- The name of the provisioning target to search    -->
  <param name="targetName" value="adUsers"/>
</customTask>

LoadGroups

The Load Groups task will load all the groups a user is a member of in Unison’s virtual directory. It can also optionally load the "inverse", only groups the user is NOT going to be a member of after this task. This can be useful when deleting a user from a group.

<customTask className="com.tremolosecurity.provisioning.customTasks.LoadGroups">
  <!-- The attribute name to search for on the user's account   -->
  <param name="nameAttr" value="uid"/>
  <!-- If set to true, only loads the groups from the virtual directory that the user's object is NOT already a member of               -->
  <param name="inverse" value="false"/>
</customTask>

LoadGroupsFromTarget

The Load Groups from Target task will load all the groups a user is a member of from a specific provisioning target. It can also optionally load the "inverse", only groups the user is NOT going to be a member of after this task. This can be useful when deleting a user from a group.

<customTask className="com.tremolosecurity.provisioning.customTasks.LoadGroupsFromTarget">
  <!-- The attribute name to search for on the user's account   -->
  <param name="nameAttr" value="uid"/>
  <!-- If set to true, only loads the groups from the virtual directory that the user's object is NOT already a member of               -->
  <param name="inverse" value="false"/>
  <!-- Name of the target -->
  <param name="target" value="some-db-target"/>
</customTask>

LoadAttributesFromTarget

The Load Attributes from Target task will load the named attributes for a user from a specific provisioning target.

    <customTask className="com.tremolosecurity.provisioning.customTasks.LoadAttributesFromTarget">
        <!-- Name of the target -->
        <param name="target" value="drupaldb"/>
        <!-- The attribute name to search for on the user's account     -->
        <param name="nameAttr" value="mail"/>
        <!-- list of attributes to load, can be listed multiple times -->
        <param name="attributes" value="uid"/>
    </customTask>

JITBasicDBCreateGroups

The Just-In-Time Create Groups task can create groups in a database table if they aren’t present. This is useful when using a database to store group information in a cloud situation where the list of groups is unknown at deployment time. It is used in conjunction with a database provisioning target that has a group table defined.

<customTask className="com.tremolosecurity.provisioning.customTasks.JITBasicDBCreateGroups">
  <!-- The name of a database provisioning taget                -->
  <param name="targetName" value="jitDB"/>
</customTask>

PrintUserInfo

The Print User Info task is useful when developing and debuging workflows. It will pring the user’s attributes to the Unison log file.

<customTask className="com.tremolosecurity.provisioning.customTasks.PrintUserInfo">
  <!-- An optional label to add to the log message      -->
  <param name="message" value="After Approval"/>
</customTask>

CreateOTPKey

Creates an OATH key, used with the Time Based One Time Password authentication mechanism.

<customTask className="com.tremolosecurity.provisioning.customTasks.CreateOTPKey">
  <!-- The name of the attribute to store the token in          -->
  <param name="attributeName" value="l"/>
  <!-- The host name of the service, used for identification in the authenticator       -->
  <param name="hostName" value="www.someplace.com"/>
  <!-- The name of the key used to encrypt and decrypt the user's token. Can be obtained from the TOTP Authentication Mechanism on your Authentication Chain.   -->
  <param name="encryptionKey" value="lastmile-enc-totp"/>
</customTask>

AddRoleTask

When used with the OpenStack Keystone provisioning target, this task makes it easier to add (or remove) a role from the user’s roles attribute. This task will generate the proper JSON. All of the configuration options are parameter aware.

<customTask className="com.tremolosecurity.unison.openstack.AddRoleTask">
  <!-- The name of the role, if not specified defaults to $role_name$ -->
  <param name="name" value="$role_name$"/>
  <!-- The scope of the role, may be project or domain -->
  <param name="scope" value="project"/>
  <!-- The name of the domain, defaults to $project_domain_name$ if not specified -->
  <param name="domain" value="$project_domain_name$"/>
  <!-- The name of the project, defaults to $project_name$ -->
  <param name="project" value="$project_name$"/>
  <!-- Set to true if the role should be removed from the user's object -->
  <param name="remove" value="false"/>
</customTask>

CreateMongoGroups

This custom task can be used in a workflow to create groups in your Mongo database that don’t exist. This is useful if you are letting users dynamically determine what groups are used for authorizing access using dynamic workflows.

<customTask
        className="com.tremolosecurity.mongodb.unison.CreateMongoGroups">
        <!-- Collection to create groups in if not found -->
        <param name="collectionName" value="groups" />
        <!-- The target to search and create groups in -->
        <param name="targetName" value="mymongodb" />
        <!-- Check a request attribute for a group name, like what might by used in a dynamic workflow -->
        <param name="requestAttributes" value="approvalGroup" />
</customTask>

CallRemoteWorkflow

Calls a remote OpenUnison to execute a workflow. Authentication is done via lastmile. This task is built to work with the com.tremolosecurity.proxy.filters.CallWorkflow filter configured on a url with the OAuth2 authentication mechanism.

<customTask className="com.tremolosecurity.provisioning.customTasks.CallRemoteWorkflow">
    <!-- Name of the workflow to call on the remote OpenUnison server -->
    <param name="workflowName" value="updateLockout"/>
    <!-- Static key used to encrypt the LAstMile token -->
    <param name="lastMileKeyName" value="lastmile-portal"/
    <!-- URL to call -->
    <param name="url" value="https://openunison.domain.lan:8443/workflows/call"/>
    <!-- Attribute from LastMile user to identify the LastMile account -->
    <param name="lastMileUid" value="uid"/>
    <!-- List of request variables to include -->
    <param name="staticRequestValues" value="UNISON.EXEC.TYPE=UNISON.EXEC.SYNC"/>
    <!-- Name of the user to use in the LastMile request -->
    <param name="lastMileUser" value="system"/>
    <!-- skew to allow for time drift across servers in millis -->
    <param name="timeSkew" value="60000"/>
    <!-- name of the attribute in te workflow object that identifies the user -->
    <param name="uidAttributeName" value="uid" />
</customTask>

AddGroupToStore

This task will add a group to a named data store. The data store MUST implement com.tremolosecurity.provisioning.core.UserStoreProviderWithAddGroup.

<customTask className="com.tremolosecurity.provisioning.customTasks.AddGroupToStore">
    <!-- The name of the target to create the group in -->
        <param name="target" value="rhelent.lan"/>
        <!-- The name of the group(s) to add, may be listed multiple times.  Values can use values from the request object -->
        <param name="name" value="created-$DYN_NAME$-workflow"/>

        <!-- Parameters passed into addGroup, may be listed multiple times -->
        <param name="attributes" value="name=value" />
</customTask>

AddGroupToRole

The AddGroupToRole task will add a group to a project role. Useful when onboarding a new project.

<customTask className="com.tremolosecurity.unison.openshiftv3.tasks.AddGroupToRole">
    <!-- Target to run against -->
        <param name="targetName" value="openshift"/>
        <!-- Project to add to, supports request parameters in between dollar signs -->
        <param name="projectName" value="$project$"/>
        <!-- Group to add supports request parameters in between dollar signs -->
        <param name="groupName" value="view-$project$"/>
        <!-- Role to add to, supports request parameters in between dollar signs -->
        <param name="roleName" value="view"/>
</customTask>

CreateProject

This task creates an OpenShift project.

<customTask className="com.tremolosecurity.unison.openshiftv3.tasks.CreateProject">
        <!-- Target to run against -->
        <param name="targetName" value="openshift"/>
        <!-- The JSON template of a ProjectRequest object, supports request parameters in between dollar signs -->
        <param name="template" value="{&quot;kind&quot;:&quot;ProjectRequest&quot;,&quot;apiVersion&quot;:&quot;v1&quot;,&quot;metadata&quot;:{&quot;name&quot;:&quot;$project$&quot;,&quot;creationTimestamp&quot;:null}}"/>
</customTask>

CopyFromUserToRequest

This task will copy attributes from a user object to the request object.

<customTask className="com.tremolosecurity.provisioning.customTasks">
        <!-- The name of the attribute to copy, may be listed multiple times -->
        <param name="attribute" value="projectName"/>
        <!-- If false, the attribute is removed from the user object -->
        <param name="keepInUser" value="false"/>
</customTask>

CreateK8sObject

Creates an object in Kubernetes using an OpenShift provisioning target. Can also be used to write to Git repositories in addition to writing directly to an api server.

<customTask className="com.tremolosecurity.provisioning.tasks.CreateK8sObject">
    <!-- An OpenShift provisioning target configured to connect to a Kubernetes API server -->
    <param name="targetName" value="k8s"/>
    <!-- determine the source type, can be json or yaml -->
    <param name="srcType" value="yaml" />
    <!-- Template for object to create, can container parameters from the request and user's object between '$' symbols -->
    <param name="template">
 <![CDATA[
kind: Namespace
apiVersion: v1
metadata:
  name: "$nameSpace$"
  labels:
    name: "$nameSpace$"
]]>
    </param>
    <!-- The type of object to create -->
    <param name="kind" value="Namespace" />
    <!-- The URI of the service to call -->
    <param name="url" value="/api/v1/namespaces" />
    <!-- A label to use in audit logs -->
    <param name="label" value="cluster" />
    <!-- If true, create new object.  If false, patch existing object if exists and create if it doesn't -->
    <param name="doPost" value="true" />

    <!-- for writing to git -->
    <!-- if true, writes the object to the workflow's request object for later processing -->
    <param name="writeToRequest" value="false" />
    <!-- if writeToRequest is true, the request object to write to -->
    <param name="requestAttribute" value="my-git-request" />
    <!-- if writeToRequest is true, the path in the git repository for the file -->
    <param name="path" value="/yaml/ns/$nameSpace$/namespace-$nameSpace$.yaml" />
</customTask>

PatchK8sObject

Patches an object in your Kubernetes cluster

<customTask className="com.tremolosecurity.provisioning.tasks.PatchK8sObject">
    <!-- An OpenShift provisioning target configured to connect to a Kubernetes API server -->
    <param name="targetName" value="k8s"/>
    <!-- Template for object to create, can container parameters from the request and user's object between '$' symbols -->
    <param name="template">
 <![CDATA[
 {
  "metadata": {
    "annotations": {
      "com.tremolosecurity.owner": "$userPrincipalName$"
    }
  }
}
]]>
    </param>
    <!-- The type of object to create -->
    <param name="kind" value="Namespace" />
    <!-- The URI of the service to call -->
    <param name="url" value="/api/v1/namespaces" />

    <!-- for writing to git -->
    <!-- if true, writes the object to the workflow's request object for later processing -->
    <param name="writeToRequest" value="false" />
    <!-- if writeToRequest is true, the request object to write to -->
    <param name="requestAttribute" value="my-git-request" />
    <!-- if writeToRequest is true, the path in the git repository for the file -->
    <param name="path" value="/yaml/ns/$nameSpace$/namespace-$nameSpace$.yaml" />
</customTask>

DeleteK8sObject

Deletes an object in your Kubernetes cluster

<customTask className="com.tremolosecurity.provisioning.tasks.DeleteK8sObject">
    <!-- An OpenShift provisioning target configured to connect to a Kubernetes API server -->
    <param name="targetName" value="k8s"/>

    <!-- The type of object to create -->
    <param name="kind" value="Namespace" />
    <!-- The URI of the service to call -->
    <param name="url" value="/api/v1/namespaces" />

    <!-- for writing to git -->
    <!-- if true, writes the object to the workflow's request object for later processing -->
    <param name="writeToRequest" value="false" />
    <!-- if writeToRequest is true, the request object to write to -->
    <param name="requestAttribute" value="my-git-request" />
    <!-- if writeToRequest is true, the path in the git repository for the file -->
    <param name="path" value="/yaml/ns/$nameSpace$/namespace-$nameSpace$.yaml" />
</customTask>

PushToGit

Pushes changes made by CreateK8sObject, PatchK8sObject, or DeleteK8sObject into git.

<customTask className="com.tremolosecurity.provisioning.tasks.PushToGit" >
  <!-- namespace where Secret that stores the ssh private key is -->
  <param name="nameSpace" value="openunison" />
  <!-- secret that stores the ssh kprivate key -->
  <param name="secretName" value="sshkey-namespace-$cluster$-$nameSpace$" />
  <!-- element of the data section of the Secret that stores the ssh private key -->
  <param name="keyName" value="id_rsa" />
  <!-- the target to use when loading the Secret -->
  <param name="target" value="k8s" />
  <!-- The git URL -->
  <param name="gitRepo" value="$namespaceGitUrl$" />
  <!-- The request object updates are stored to -->
  <param name="requestObject" value="git-secret-namespace-$cluster$-$nameSpace$" />
  <!-- The commit message -->
  <param name="commitMsg" value="For workflow $WORKFLOW.id$" />
</customTask>

CleanLabels

Cleans request attributes that are meant to become labels on Kubernetes objects. Labels must be complient with DNS host names. This task will replace invalid characters, and inject an "x" before or after the label if the leading or trailing characters don’t properly match.

<customTask className="com.tremolosecurity.provisioning.tasks.CleanLabels" >
  <!-- Character that will replace invalid characters in the named attributes -->
  <param name="replacementCharacter" value="-" />
  <!-- The suffix to use on the new request attribute.  For instance if cleaning "rawlabel", and setting this to "_clean", you would reference "rawlabel_clean" in the tasks for creating objects. -->
  <param name="newAttributeSuffix" value="_clean" />
  <!-- Request attributes to be cleaned, may be listed multiple times -->
  <param name="attributes" value="raw label" />

</customTask>

ClearGroups

Deletes all groups from a user’s object

<customTask className="com.tremolosecurity.provisioning.tasks.ClearGroups">

</customTask>

Env2Req

Coppies environment variables to the workflow’s request object. The name of the param is the name of the object in the workflow request to create, the value is the name of the environment variable to get the value from.

<customTask className="com.tremolosecurity.provisioning.tasks.Env2Req">
    <param name="for_request" value="from_environment" />
</customTask>

ClearPasswordResets

This task will clear out all password reset requests for the user.

<customTask className="com.tremolosecurity.provisioning.customTasks.ClearPasswordResets">
        <!-- The name of the password reset mechanism  -->
  <param name="mechName" value="passwordReset"/>
</customTask>

CopyGroupMembers

CopyGroupMembers will copy the members from one group to another. This is useful when dynamiclly generating access control groups from a workflow.

<customTask className="com.tremolosecurity.provisioning.customTasks.CopyGroupMembers">
  <!-- a workflow for performing the copy (see example) -->
  <param name="copyWorkflow" value="addApproverUsers"/>
  <!-- The name of the group to copy members to inside of a provisionng target -->
  <param name="copyTo" value="approvers-openshift-$name$" />
  <!-- The group in the virtual directory that is the source for members -->
  <param name="copyFrom" value="cn=administrators,ou=groups,ou=shadow,o=Tremolo" />
  <!-- The name of the user id attribute -->
  <param name="uidAttributeName" value="uid" />
  <!-- The requester for the audit trail -->
  <param name="requestor" value="system" />
</customTask>

Example copyWorkflow:

<workflow name="addApproverUsers" label="Add approver users" description="Add new approval users" inList="false" orgid="63ada052-881e-4685-834d-dd48a3aa4bb4">
    <tasks>
        <mapping  strict="true">
          <map>
            <mapping targetAttributeName="sub" sourceType="user" targetAttributeSource="uid"/>
          </map>
            <onSuccess>
                <provision sync="false" target="jitdb" setPassword="false" onlyPassedInAttributes="false">
                    <attributes>
                        <value>sub</value>
                    </attributes>
                </provision>
            </onSuccess>
        </mapping>
    </tasks>
</workflow>

DoesGroupExist

This task will check to see if a group exists in a target, and put the result in a request parameter.

<customTask className="com.tremolosecurity.provisioning.customTasks.DoesGroupExist">
  <!-- The provisioning target to check -->
  <param name="target" value="jitdb"/>
  <!-- The name to check -->
  <param name="groupName" value="approvers-openshift-$name$" />
  <!-- The name of the request attribute to create -->
  <param name="attributeName" value="tremolo.approval.group.exists" />
</customTask>

GenUUIDAttribute

Useful way to generate a unique id.

<customTask className="com.tremolosecurity.provisioning.customTasks.com.tremolosecurity.provisioning.customTasks">
        <!-- Name of the attribute to put the uuid in -->
        <param name="attributeName" value="uuid" />
</customTask>

MapJitGroups

Its often useful to map from an external group to internal group when just-in-time provisioning group access. This task provides a static map between groups.

  <customTask className="com.tremolosecurity.provisioning.customTasks.MapJitGroups">
          <!-- The name of the attribute that stores the user's groups -->
    <param name="attributeName" value="memberOf"/>
          <!-- Each mapping is of the form internalgroup=externalgroup -->
    <param name="groupMap" value="k8s-cluster-administrators=CN=jit-k8s-admin,CN=Users,DC=ent2k12,DC=domain,DC=com" />
          <param name="groupMap" value="administrators=CN=ouadmins,CN=Users,DC=ent2k12,DC=domain,DC=com" />
        </customTask>

AddGitlabExternalIdentity

This task makes it easier to add an external identity without writing code. Useful in JIT workflows.

<customTask className="com.tremolosecurity.unison.gitlab.provisioning.tasks.AddGitlabExternalIdentity">
    <!-- Which omni_auth provider to use -->
    <param name="provider" value="openid_connect" />
    <!-- The attribue in the user object to map the user's identity to -->
    <param name="userAttribute" value="username" />
</customTask>

AddGroupToProject

This task will add an existing group to a project and set its entitlements on that project. The group will be added to the last project created by the CreateProject task.

<customTask className="com.tremolosecurity.unison.gitlab.provisioning.tasks.AddGroupToProject">
    <!-- Group to add -->
        <param name="groupName" value="approvers-k8s-$nameSpace$" />
        <!-- GitLab provisioning target -->
    <param name="targetName" value="gitlab" />
    <!-- Entitlement level for the group in the project -->
    <param name="accessLevel" value="MAINTAINER" />
</customTask>

CreateDeploymentKey

CreateDeploymentKey will create a deployment key on a project, making the key and its base64 encoded value available in the workflow’s request object.

<customTask className="com.tremolosecurity.unison.gitlab.provisioning.tasks.CreateDeploymentKey">
  <!-- GitLab provisioning target -->
  <param name="targetName" value="gitlab" />
  <!-- project namespace -->
  <param name="namespace" value="$nameSpace$-production" />
  <!-- project name -->
  <param name="project" value="$nameSpace$-application" />
  <!-- label for the key -->
  <param name="keyLabel" value="tekton_pull" />
  <!-- if the key is writeable or read only -->
  <param name="makeWriteable" value="false" />
  <!-- The name of the request object the key is stored in, base64 encoded-->
  <param name="privateKeyReuestName" value="tektonPullecret" />
  <!-- The name of the request object the key is stored in, plain text -->
  <param name="privateKeyReuestNamePT" value="tektonPullSecretPT" />
</customTask>

CreateGitFile

Creates a file in the named project.

<customTask className="com.tremolosecurity.unison.gitlab.provisioning.tasks.CreateGitFile">
    <!-- GitLab target -->
    <param name="targetName" value="gitlab" />
    <!-- project namespace -->
    <param name="namespace" value="$nameSpace$-production" />
    <!-- project name -->
    <param name="project" value="$nameSpace$-application" />
    <!-- branch to commit against -->
    <param name="branch" value="master" />
    <!-- path and file (excluding "/") -->
    <param name="path" value="README.md" />
    <!-- content of the file to create -->
    <param name="content">
  <![CDATA[
  # $nameSpace$-application

  Fork this project to create to create your application.  Create a pull request to trigger a build and deployment to development.

  ]]>
    </param>
    <!-- commit message -->
    <param name="commitMessage" value="initializing the repository" />
</customTask>

CreateProject

Creates a GitLab project. Can optionally create a webhook and generate a deployment key.

<customTask className="com.tremolosecurity.unison.gitlab.provisioning.tasks.CreateProject">
    <!-- project namespace -->
    <param name="namespace" value="$nameSpace$-production" />
    <!-- project name -->
    <param name="name" value="$nameSpace$-application" />
    <!-- project description -->
    <param name="description" value="Application project" />
    <param name="issuesEnabled" value="true" />
    <param name="mergeRequestsEnabled" value="true" />
    <param name="wikiEnabled" value="true" />
    <param name="snipitsEnabled" value="true" />
    <param name="visibility" value="2" />
    <param name="targetName" value="gitlab" />
    <param name="gitSshHost" value="#[GITLAB_SSH_HOST]" />
    <param name="createWebhook" value="true" />
    <param name="webhookSuffix" value="#[GITLAB_WEBHOOK_SUFFIX]" />
    <param name="webhookBranchFilter" value="master" />
    <param name="webhookSecretRequestName" value="appProjectWebhook"/>
</customTask>

ForkProject

Forks a GitLab project into another namespace.

<customTask className="com.tremolosecurity.unison.gitlab.provisioning.tasks.ForkProject">
    <param name="sourceProjectName" value="$nameSpace$-operations" />
    <param name="sourceProjectNamespace" value="$nameSpace$-production" />
    <param name="destinationNamespace" value="$nameSpace$-dev" />
    <param name="targetName" value="gitlab" />
    <param name="gitSshHost" value="#[GITLAB_SSH_HOST]" />
</customTask>

CreateGitRepository

Creates a git repository in ArgoCD, registering an SSH private key.

<customTask className="com.tremolosecurity.argocd.tasks.CreateGitRepository">
  <!-- type of repository -->
  <param name="type" value="git" />
  <!-- name of the repository -->
  <param name="name" value="$nameSpace$-build" />
  <!-- ssh URL for the repository -->
  <param name="repoUrl" value="$gitSshInternalURL$" />
  <!-- Plain text encoded SSH private key to register -->
  <param name="sshPrivateKey" value="$gitPrivateKey$" />
  <!-- ArgoCD target name -->
  <param name="target" value="argocd" />
</customTask>

AddtoRBAC

Appends to the RBAC ConfigMap in ArgoCD.

<customTask className="com.tremolosecurity.argocd.tasks.AddtoRBAC">
  <!-- Kubernetes target -->
  <param name="k8sTarget" value="k8s" />
  <!-- rules to add -->
  <param name="toAdd">
    <![CDATA[
p, role:$nameSpace$-operations, applications, get, $nameSpace$/*, allow
p, role:$nameSpace$-operations, applications, override, $nameSpace$/*, allow
p, role:$nameSpace$-operations, applications, sync, $nameSpace$/*, allow
p, role:$nameSpace$-operations, applications, update, $nameSpace$/*, allow

p, role:$nameSpace$-operations, projects, get, $nameSpace$, allow

g, k8s-namespace-operations-$nameSpace$, role:$nameSpace$-operations


p, role:$nameSpace$-dev, applications, get, $nameSpace$/*, allow
p, role:$nameSpace$-dev, projects, get, $nameSpace$, allow
g, k8s-namespace-developer-$nameSpace$, role:$nameSpace$-dev
                          ]]>
  </param>
</customTask>

Message Listeners

This section details the pre-built message listeners. These tasks can be used in your deployments without change. Consult the Unison SDK for instructions on how to create message listeners.

All listeners have a common interface for specifying configuration options. Each task can take any number of name/value pairs. A single configuration option can have multiple values by listing the name/value pair for each value.

UpdateApprovalAZListener

This listener is used in conjunction with the Update Authorizations scheduled task. The scheduled task identifies open approvals and places those approvals on the queue. This listener picks those approvals up and re-sets the allowed approvers.

<!-- queueName - The name of the queue to listen to -->
<listener className="com.tremolosecurity.provisioning.listeners.UpdateApprovalAZListener" queueName="rebaseQueue"></listener>

AutoFailApprovalListener

This listener is used in conjunction with the Automatically Fail Open Approvals scheduled task. The scheduled task identifies which open approvals are assigned to the failure user and adds those approvals to the queue. This listener executes the failure of those requests.

<!-- queueName - The name of the queue to listen to -->
<listener className="com.tremolosecurity.provisioning.listeners.AutoFailApprovalListener" queueName="failQueue"></listener>

AsyncExecuteWorkflow

Use this listener to execute workflows asynchronously, usually from a scheduled jobs. In order to run a workflow, enqueue a WFCall object.

<listener className="com.tremolosecurity.provisioning.listeners.AsyncExecuteWorkflow" queueName="runworkflows"></listener>

PullListener

The PullListener receives Prometheus metrics requests via a JMS queue instead of HTTP and then based on the configuration will pull in metrics requests for several remote instances. This is designed to provide the ability to have a remove Prometheus monitor an environment without an ingress into the network.

<listener className="com.tremolosecurity.prometheus.aggregate.PullListener" queueName="prometheus-request.fifo"> <!-- base64 encoded gzip of url data for what metrics endpoings to check (see below) -→ <params name="urls" value="[PROMETHEUS_URLS]" /> <!-- Response queue -→ <params name="sendToQueueName" value="prometheus-response.fifo" /> <!-- Implementation of com.tremolosecurity.prometheus.sdk.AdditionalMetrics -→ <params name="additionalMetricsClassName" value="com.domain.ExtraChecks" /> <!-- Additional parameters for extra metrics -→ <params name="dlqName" value="dlq.fifo" /> <params name="loginTestURL" value="[PROMETHEUS_LOGIN_URL]" /> <params name="loginTestUserName" value="[PROMETHEUS_LOGIN_USERNAME]" /> <params name="loginTestPassword" value="[PROMETHEUS_LOGIN_PASSWORD]" /> <params name="internal" value="true" /> </listener>

The urls setting

The urls setting is a base64 encoded gzip of JSON describing what metrics endpoints should be pulled. Below is an example of the JSON that needs to be gzipped and base64 encoded"

[ { "url": "https://X.X.X.X:8443/metrics", "cluster": "unison", "ipAddress": "X.X.X.X", "clusterLabel": "remote_job", "ipLabel": "remote_instance", "lastMileAuthentication": true, "lastMileKeyName": "lastmile-prometheus", "lastMileUidAttributeName": "uid", "injectIpAndCluster": true, "timeout": 2000, "lastMileTimeSkewSeconds": 30, "lastMileUid": "prometheus" }, { "url": "https://X.X.X.X:8443/node-metrics", "cluster": "node", "ipAddress": "X.X.X.X", "clusterLabel": "remote_job", "ipLabel": "remote_instance", "lastMileAuthentication": true, "lastMileKeyName": "lastmile-prometheus", "lastMileUidAttributeName": "uid", "injectIpAndCluster": true, "timeout": 2000, "lastMileTimeSkewSeconds": 30, "lastMileUid": "prometheus" } ]

This file will then be gzipped and base64 encoded :

cat /path/to/json | base64 | gzip

This generates an output of the above sample :

H4sIAK0t8VwAA+1RwUrEMBC99ytCz7u2uHuQ3ooncdfLKggi0iYPOmuTlGSCiPjvJqtdl4DgxZuEXOa9eW9m3kMhxFv8QpTBjWUjyoF58k1V3Z8dXnOxXq8qDXYkfbn4pMoxeIZL9GDIWzMDNLVKOXifoC+FrGnT9TgYOWjLeNrb/rs7B8l47ozEzBg7z1sa0QYeYJhkxxTdG8EuIONc4/Wm00hqqaRjaTk5G1cZEHyueEeq5bhkHxhzWyB1HM3sIflqao26PC5/YsqkYQPH4nld15n2bQR3z3jZQVqj0m1WOSXaJ8eT+SL+vvhdOsYqLH+OKMH/Af1JQMVj8QH5800qQgMAAA==

Scheduled Jobs

This section details the pre-built Unison jobs. These jobs can be used in your deployments without change. Consult the Unison SDK for instructions on how to create custom jobs.

All jobs have a common interface for specifying configuration options. Each job can take any number of name/value pairs. A single configuration option can have multiple values by listing the name/value pair for each value.

UpdateApprovalAz

This job evaluates all open approvals and resets the allowed approvers based on the configured authorizations in the workflow. This allows for any changes to members of groups or that a filter would apply to to be reflected in the list of open approvers. This job is used in conjunction with the Update Approvals Authorizations message listener. The job finds all open approvals and adds each approval to the queue. The Update Approvals Authorizations message listener then updates the allowed approvers for each open approval.

<job className="com.tremolosecurity.provisioning.scheduler.jobs.UpdateApprovalAz" name="resetAllowedApprovers" group="testing">
  <!-- When to run the job -->
        <cronSchedule
                seconds="0"
                minutes="0"
                hours="*"
                dayOfMonth="*"
                month="*"
                dayOfWeek="?"
                year="*"
        />
  <!-- The name of the queue the Update Approvals Authorizations message listener is configured on      -->
        <param name="queueName" value="rebaseQueue" />
</job>

RemindApprovers

This job provides a mechanism for reminding users that they have open approvals waiting for their action.

<job className="com.tremolosecurity.provisioning.scheduler.jobs.RemindApprovers" name="remindAppovers" group="testing">
  <!-- When to run the job -->
        <cronSchedule
                seconds="0"
                minutes="0"
                hours="9"
                dayOfMonth="*"
                month="*"
                dayOfWeek="?"
                year="*"
        />

  <!-- The template for the message to send to the approvers. Use %L to represent the label of the workflow that is open and %D to represent the number of days open    -->
  <param name="message" value="The request %L has been open for %D days, please login to act on this request" />
  <!-- The number of days an approval request should be open before sending a reminder  -->
  <param name="days" value="7" />
  <!-- The name of the attribute that has the user's email address      -->
  <param name="mailAttributeName" value="mail" />
</job>

AutoFail

This job identifies all open approvals assigned to a specific user and marks them to be declined. The job takes these approval requests and puts them on a queue to be picked up by the Automatically Fail Open Approvals listener.

<job className="com.tremolosecurity.provisioning.scheduler.jobs.AutoFail" name="autoFail" group="autoFail">
        <cronSchedule
                seconds="0"
                minutes="0"
                hours="*"
                dayOfMonth="*"
                month="*"
                dayOfWeek="?"
                year="*"
        />
  <!-- The name of the queue to add approval requests to        -->
        <param name="queueName" value="failQueue" />
  <!-- A message to be provided to users as to why the request failed   -->
        <param name="approver" value="autoFail" />
  <!-- The name of the user whom all failures are assigned, must be the value of the user identifier attribute configured on the audit database -->
        <param name="message" value="failed : ${reason}" />
</job>

DeleteObject

This job will delete a Kubernetes/OpenShift object. Its most useful when a distrobution continously creates objects that are not needed or wanted on a consistent basis.

<job className="com.tremolosecurity.unison.openshiftv3.jobs.DeleteObject" name="deleteSelfProvisioner" group="management">
  <cronSchedule
                seconds="0"
                minutes="*"
                hours="*"
                dayOfMonth="*"
                month="*"
                dayOfWeek="?"
                year="*"
  />
  <!-- The name of the provisioning target -->
  <param name="target" value="openshift" />
  <!-- The URI of the object to delete -->
  <param name="uri" value="/apis/authorization.openshift.io/v1/clusterrolebindings/self-provisioners" />
</job>

Custom Authorization rules

This section details the pre-built Unison custom authorization rules. These rules can be used in your deployments without change. Consult the Unison SDK for instructions on how to create custom custom authorization rules.

All custom authorization rules have a common interface for specifying configuration options. Each rule can take any number of name/value pairs. A single configuration option can have multiple values by listing the name/value pair for each value.

ManagerAuthorization

This rule provides a mechanism for allowing a user’s supervisor or manager to approve a request. The approver is authorized based on the user’s data in Unison’s internal virtual directory.

<azRule name="managerLevel1" className="com.tremolosecurity.provisioning.az.ManagerAuthorization">
  <!-- Use this to define what "distance" of a manager is authorized. For instance to allow the user's manager's manager to perform the authorization specify "2". This allows for escalations to be processed with multiple tiers of managers. -->
        <params name="numLevels" value="1" />
  <!-- The name of the attribute on the user's directory object that will tell Unison who their manager is.     -->
        <params name="managerID" value="manager" />
  <!-- If checked, Unison will assume that the attribute defined in "Manager Attribute Name" is the distinguished name of the user's manager. If not checked, then Unison will use a filter built from the "User Identifier Attribute" from the Approvals screen and the value of the "Manager Attribute Name"  -->
        <params name="managerIDIsDN" value="true" />
  <!-- Used when the number of levels is greater then one, allowing all the managers between the user and the current step to approve. For instance of an approval is escalated to a user's manager's manager checking this option will allow both the user's manager AND the user's manager's manager to approve.      -->
        <params name="allowLowerManagers" value="false" />
</azRule>

GithubTeamRule

This rule is a convinience for authorizing multiple GitHub orgnaizations and teams without listing multiple LDAP filters. In the rule, list each team as org/team and each org as org/.

<azRule name="github" className="com.tremolosecurity.unison.proxy.auth.github.GithubTeamRule"/>

ClearSessions (OpenID Connect)

This scheduled job will clear our stale sessions.

<job className="com.tremolosecurity.idp.providers.oidc.model.jobs.ClearSessions" name="clearSessions" group="admin">
  <cronSchedule
    seconds="0"
    minutes="0/5"
    hours="*"
    dayOfMonth="*"
    month="*"
    dayOfWeek="?"
    year="*"
  />
  <!-- The name of the identity provider to clear sessions for -->
  <param name="idpName" value="oidc" />
</job>

Identity Providers

Unison supports multiple identity provider implementations. Each identity provider has its own configuration. This section details how to configure an individual type of identity provider. Each identity provider type has a global configuration, which is on the ?URL? screen, and a trust configuration which tells Unison how to provide information for a particular partner.

Saml2Idp

SAML2 is a standard form of federation that is very popular in enterprise environments. Unison can act as a SAML2 identity provider providing SAML2 assertions, attributes and strong security. The SAML2 identity provider supports signing and encrypting of assertions.

<application isApp="false" name="saml2">
        <results />
        <urls>

                <url regex="false">
      <!-- Set the hosts for the IdP -->
                        <host>localhost.localdomain</host>
      <!-- List filters, these filters should focus on manipulating the authenticated user's account -->
                        <filterChain>
                                <filter class="com.tremolosecurity.test.util.AddGroupFilter" />
                                <filter class="com.tremolosecurity.prelude.filters.Group2Attribute">
                                        <param name="attributeName" value="role" />
                                        <param name="attributeValue" value="users" />
                                        <param name="groupDN" value="cn=linkedSAMLUsers,ou=internal,ou=GenericLDAP,o=Tremolo" />
                                </filter>
                        </filterChain>
      <!-- URI for the IDP should ALLWAYS be this value -->
                        <uri>/auth/idp/saml2</uri>
                        <results>
                                <auSuccess></auSuccess>
                                <auFail>failure</auFail>
                                <azSuccess></azSuccess>
                                <azFail>failure</azFail>
                        </results>
      <!-- Authorize who has access to this IdP -->
                        <azRules>
                                <rule constraint="(objectClass=*)" scope="filter" />
                        </azRules>
      <!-- IdP Specific Configuration -->
                        <idp className="com.tremolosecurity.idp.providers.Saml2Idp">
        <!-- The key used for encrypting authentication requests. The key should be listed under the ?Signature and Encryption Keys? in Certs   -->
                                <params value="" name="encKey" />
        <!-- The key used for verifying signed authentication requests. The key should be listed under the ?Signature and Encryption Keys? in Certs     -->
                                <params value="idp-sig-key" name="sigKey" />
        <!-- Must the identity provider require signed metadata? If not checked, signed metadata will still be accepted and validated.  -->
        <param name="requireSignedAuthn" value="true" />
        <!-- Template for the SAML2 Post page -->
        <param name="postTemplate" value="&lt;html&gt;\n&lt;head&gt;\n&lt;meta http-equiv=\&quot;Content-Type\&quot; content=\&quot;text/html; charset=UTF-8\&quot; /&gt;\n&lt;title&gt;Completing Federation&lt;/title&gt;\n&lt;/head&gt;\n&lt;body onload=\&quot;document.forms[0].submit()\&quot;&gt;\n&lt;form method=\&quot;post\&quot; action=\&quot;$postaction$\&quot;&gt;\n&lt;input name=\&quot;SAMLResponse\&quot; value=\&quot;$postdata$\&quot; type=\&quot;hidden\&quot;/&gt;\n&lt;input name=\&quot;RelayState\&quot; value=\&quot;$relaystate$\&quot; type=\&quot;hidden\&quot;/&gt;\n&lt;/form&gt;\n&lt;center&gt;\n&lt;img src=\&quot;/auth/forms/images/ts_logo.png\&quot; /&gt;&lt;br /&gt;\n&lt;h2&gt;Completing Federation...&lt;/h2&gt;\n&lt;/center&gt;\n&lt;/body&gt;\n&lt;/html&gt;" />
        <!-- Mappings for attributes to be included in the assertion -->
        <!-- strict - If true then ONLY attributes specificly named in the mapping will be added -->
        <mappings strict="true">
                                        <mapping sourceType="user" targetAttributeSource="groupName"
                                                targetAttributeName="groupName" />
                                        <mapping sourceType="static" targetAttributeSource="Boston"
                                                targetAttributeName="l" />
                                        <mapping sourceType="user" targetAttributeSource="role" targetAttributeName="role" />
                                </mappings>
        <!-- Trusts establish connections with remote service providers -->
                                <trusts>
          <!--
            A trust establishes a connection between the SAML2 IdP and a SAML2 SP. The trust configuration establishes this connection by specifying URLs, certificates and mappings from nameid and authentication types to attributes and authentication chains respectively. The name of the trust must match with the issuer in a saml response or assertion. The only profile supported by Unison is the HTTP-POST profile.
           -->
                                        <trust name="http://shib2x.tremolo.lan/shibboleth">
            <!-- The URL used to post the response to. This is optional if included in the authentication request.      (AssertionConsumerService) -->
                                                <param
                                                        value="http://shib2x.tremolo.lan/Shibboleth.sso/SAML2/POST"
                                                        name="httpPostRespURL" />

            <!-- The key used to sign the response or assertion -->
            <param value="" name="spSigKey" />
            <!-- The key used to encrypt assertions     -->
                                                <param value="" name="spEncKey" />
            <!-- Determine if the assertion should be signed. If encrypting assertions, its expected that the assertion will be signed. -->
                                                <param value="false" name="signAssertion" />
            <!-- Should the entire response (including the assertion)   -->
                                                <param value="true" name="signResponse" />
            <!-- If no nameid format is specified in the authentication request or in the idp initiated request this setting specifies which attribute to use to identify the user      -->
                                                <param value="urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified"
                                                        name="defaultNameId" />
            <!-- If no authentication class context reference is specified in the authentication request or in the idp initiated request specifies how to authenticate the user -->
            <param
                                                        value="urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport"
                                                        name="defaultAuthCtx" />
            <!-- map a requested nameid format to an attribute to look the user up by.  may be lsited multiple times, but must be listed AT LEAST once.  The default name id MUST be included -->
            <param value="urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified=uid"
                                                        name="nameIdMap" />
            <!-- map a requests authentication context class ref to an authentication chain.  may be listed multiple times but must be listed AT LEAST once.  The default authnContextClassRef must be included -->
            <param
                                                        value="urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport=form"
                                                        name="authCtxMap" />
                                        </trust>
                                </trusts>
                        </idp>
                </url>
        </urls>
        <cookieConfig>
                <sessionCookieName>tremoloSession</sessionCookieName>
                <domain>localhost.localdomain</domain>
                <scope>-1</scope>
                <logoutURI>/logout</logoutURI>
                <keyAlias>session-tremolosession</keyAlias>
                <keyPassword></keyPassword>
    <sameSite>None</sameSite>
        </cookieConfig>
</application>

The SAML2 IdP has the following URLs:

URI

Function

/auth/idp/saml2/httpPost

POST Profile assertion consumer service URI

/auth/idp/saml2/httpRedirect

Redirect Profile assertion consumer service URI

/auth/idp/saml2/idpInit?sp=https://saml2.salesforce.com

IdP Initated URL where the sp parameter is the name of the trust defined in the identity provider

OpenIDConnectIdP

The OpenIDConnect Identity Provider provides the features of OpenID Connect most often parts of the Authorization code flow used by applictions. This implementation is designed to be compatible with Google’s documentation for OpenID Connect integration. OpenUnison provides authentication, token validation and refresh. Additionally, the OpenID Connect discovery profile is supported. Once configured, the com.tremolosecurity.idp.providers.oidc.model.jobs.ClearSessions job should be configured in the scheduler to make sure that old sessions are properly cleared out.

This IdP has a pluggable backend architecture that supports three backends:

com.tremolosecurity.idp.providers.oidc.db.DbOidcSessionStore

A relational database is used to store session state

com.tremolosecurity.oidc.k8s.K8sSessionStore

Session state is stored in Kubernetes as custom resources

com.tremolosecurity.idp.providers.oidc.none.NoneBackend

No session state is stored, this is useful in situations where refresh tokens are never used

<application name="oidc" isApp="false">
  <!-- on a single URL is allowed on an identity provider -->
  <urls>
    <!-- The regex and authChain attributes are ignored -->
    <url regex="false">
      <!-- Any number of host tags may be specified to allow for an application to work on multiple hosts.  Additionally an asterick (*) can be specified to make this URL available for ALL hosts -->
      <host>mlb.tremolo.lan</host>
      <!-- The filterChain on an IdP is typically used to add attributes to the user prior to mapping into the assertion -->
      <filterChain>
        <filter class="com.tremolosecurity.prelude.filters.Groups2Attribute">
          <!-- The base in the virtual directory to begin searching for groups.  -->
          <param name="base" value="ou=groups,ou=mongo,dc=domain,dc=com" />
          <!-- Name of the attribute to create  -->
          <param name="attrName" value="user_role" />
          <!-- A regular expression to filter out group memberships     -->
          <param name="pattern" value=".*" />
          <!-- If a pattern is specified, the group from that pattern to add to the attribute   -->
          <param name="groupNum" value="0" />
        </filter>
      </filterChain>
      <!-- The URI MUST start with /auth/idp/ -->
      <uri>/auth/idp/oidc</uri>
      <!-- List the various results that should happen -->
        <results>
          <auSuccess></auSuccess>
          <auFail>Default Login Failure</auFail>
          <azSuccess></azSuccess>
          <azFail>Default Login Failure</azFail>
        </results>
        <!-- Determine if the currently logged in user may access the idp.  If ANY rule succeeds, the authorization succeeds.
          The scope may be one of group, dn, filter, dynamicGroup or custom
          The constraint identifies what needs to be satisfied for the authorization to pass and is dependent on the scope:
            * group - The DN of the group in OpenUnison's virtual directory (must be an instance of groupOfUniqueNames)
            * dn - The base DN of the user or users in OpenUnison's virtual directory
            * dynamicGroup - The DN of the dynamic group in OpenUnison's virtual directory (must be an instance of groupOfUrls)
            * custom - An implementation of com.tremolosecurity.proxy.az.CustomAuthorization -->
        <azRules>
            <rule scope="filter" constraint="(objectClass=*)"/>
        </azRules>
        <!-- Defines the IdP specific portions of the application -->
        <idp className="com.tremolosecurity.idp.providers.OpenIDConnectIdP">
          <!-- The alias of the TLS key to use for signing JWTs -->
          <params name="jwtSigningKey" value="unison-tls"/>

          <!-- optional, transform outbound claims before encoding -->
          <!--
          <!-- Subclass of com.tremolosecurity.idp.providers.oidc.sdk.UpdateClaims -->
          <param name="updateClaimsClassName" value="com.path.to.Class" />

          -->


          <!-- BEGIN DATABASE SPECIFIC CONFIGURATION OPTIONS -->
          <!-- Database driver -->
              <params name="driver" value="com.mysql.jdbc.Driver"/>
              <!-- JDBC URL -->
              <params name="url" value="jdbc:mysql://192.168.99.100:3306/unison?useSSL=true"/>
              <!-- DB User -->
              <params name="user" value="root"/>
              <!-- DB Password -->
              <params name="password" value="start123"/>
              <!-- Maximum number of connections -->
              <params name="maxCons" value="5"/>
              <!-- Maximum number of connections not actively working -->
              <params name="maxIdleCons" value="5"/>
              <!-- The HibernateSQL dialect -->
              <params name="dialect" value="org.hibernate.dialect.MySQL5Dialect"/>
              <!-- Validation query to make sure the connection is still available -->
              <params name="validationQuery" value="SELECT 1"/>
              <!-- Optional - The mapping file (.hbx.xml) to use if the default mapping doesn't work, must be in the classpath -->
              <tns:param name="hibernateConfig" value=""/>
              <!-- Optional - If true, create tables.  After initial configuration, you may want to disable this depending on the dialect. -->
              <tns:param name="hibernateCreateSchema" value="true"/>
                  <!-- END DATABASE SPECIFIC CONFIGURATION OPTIONS -->



                <!-- UNCOMMENT FOR KUBERNETES BACKEND

                <!-- The session implementation -->
                <params name="sessionStoreClassName" value="com.tremolosecurity.oidc.k8s.K8sSessionStore"/>
        <!-- OpenShift (Kubernetes) Provisioning Target -->
        <params name="k8sTarget" value="k8s" />
        <!-- The namespace to store/retrieve sessions from -->
        <params name="k8sNameSpace" value="openunison" />


                -->


                <!-- UNCOMMENT FOR NO BACKEND

                <!-- The session implementation -->

                <params name="sessionStoreClassName" value="com.tremolosecurity.idp.providers.oidc.none.NoneBackend"/>

        -->

         <!-- UNCOMMENT for Kubernetes dynamic trusts -->
         <!--
        <params name="trustConfigurationClassName" value="com.tremolosecurity.oidc.k8s.K8sLoadTrusts" />
        <params name="trusts.k8starget" value="k8s" />
        <params name="trusts.namespace" value="#[K8S_OPENUNISON_NS:openunison]" />
         -->



          <!-- Determines which attributes to include in the assertion. -->
          <!-- strict - If true then ONLY attributes specificly named in the mapping will be added -->
          <mappings strict="true">
             <!-- Each mapping focusses on a single attribute
               targetAttributeName - The name of the attribute that will appear in the assertion
               sourceType - One of user, static, or composite
                * user - The name of an existing attribute on the user's object
                * static - A static value that does not change regardless of the user
                * composite - A mixture of user and static, allowing for one SAML attribute to be comprised of other attributes and static text.  Attributes are market as "${attributename}"
               targetAttributeSource - The value to be used based on the sourceType
             -->
            <mapping targetAttributeName="sub" targetAttributeSource="uid" sourceType="user"/>
            <mapping targetAttributeName="email" targetAttributeSource="mail" sourceType="user"/>
            <mapping targetAttributeName="user_role" targetAttributeSource="user_role" sourceType="user"/>
          </mappings>
          <!-- Trusts establish a path between the OP and SP/RP -->
          <trusts>
            <!-- The name of the trust, should be the url of the RP -->
            <trust name="http://192.168.99.100">
              <!-- used to identify the client, use this name when configuring filters that reference this trust -->
              <param name="clientID" value="mod_oidc_test"/>
              <!-- A secret shared by both OpenUnison and the client -->
              <param name="clientSecret" value="secret"/>
              <!-- If checked, no client secret is used to authenticate requests -->
              <param name="publicEndpoint" value="false"/>
              <!-- The URL OpenUnison will redirect users to after authentication -->
              <param name="redirectURI" value="http://192.168.99.100/redirect_uri"/>
              <!-- LastMile key used to encrypt code and refresh tokens -->
              <param name="codeLastMileKeyName" value="session-unison"/>
              <!-- The authentication chain to use for authentication -->
              <param name="authChainName" value="formloginFilter"/>
              <!-- Milliseconds an access token and id_token lives until it needs to be refreshed -->
              <param name="accessTokenTimeToLive" value="60000"/>
              <!-- Milliseconds that a token can be off from the current time that will be accepted -->
              <param name="accessTokenSkewMillis" value="120000" />
              <!-- The number of milliseconds the code token is valid -->
              <param name="codeTokenSkewMilis" value="90000"/>
              <!-- In a dev environment where developers don't know their host name or need to try with multiple hosts and ports, its useful to disable redirect_uri checking. -->
              <!-- DO NOT SET THIS TO FALSE UNLESS IT IS A DEV ENVIRONMENT -->
              <!-- SETTING THIS TO FALSE IS A SECURITY ISSUE -->
              <param name="verifyRedirect" value="true" />

              <!-- Should the userinfo endpoint be signed?  defaults to false -->
              <param name="signedUserInfo" value="false" />
            </trust>
            <trust name="https://172.17.4.99">
              <param name="clientID" value="kubernetes"/>
              <param name="clientSecret" value="secret"/>
              <param name="redirectURI" value="https://172.17.4.99/redirect_uri"/>
              <param name="codeLastMileKeyName" value="session-unison"/>
              <param name="authChainName" value="formloginFilter"/>
              <param name="codeTokenSkewMilis" value="90000"/>
              <param name="accessTokenTimeToLive" value="60000"/>
              <param name="accessTokenSkewMillis" value="120000" />
            </trust>
          </trusts>
        </idp>
      </url>
    </urls>
    <!-- The cookie configuration determines how sessions are managed for this application -->
    <cookieConfig>
      <!-- The name of the session cookie for this application.  Applications that want SSO between them should have the same cookie name -->
      <sessionCookieName>tremolosession</sessionCookieName>
      <!-- The domain of component of the cookie -->
      <domain>mlb.tremolo.lan</domain>
      <scope>-1</scope>
      <!-- The URL that OpenUnison will interpret as the URL to end the session -->
      <logoutURI>/logout</logoutURI>
      <!-- The name of the AES-256 key in the keystore to use to encrypt this session -->
      <keyAlias>session-unison</keyAlias>
      <!-- If set to true, the cookie's secure flag is set to true and the browser will only send this cookie over https connections -->
      <secure>true</secure>
      <!-- The number of seconds that the session should be allowed to be idle before no longer being valid -->
      <timeout>0</timeout>
    </cookieConfig>
</application>

The OpenID Connect IdP has the following URLs:

URI

Function

/auth/idp/oidc/.well-known/openid-configuration

OpenID Connect Discovery URL

Kubernetes Dynamic Trusts

You can dynamicly add trusts to your IdP by creating Trust objects in the namespace that OpenUnison is in.

apiVersion: openunison.tremolo.io/v1
kind: Trust
metadata:
  name: gitlab
  namespace: openunison
spec:
  accessTokenSkewMillis: 120000
  accessTokenTimeToLive: 60000
  authChainName: LoginService
  clientId: gitlab
  clientSecret:
    keyName: gitlab
    secretName: orchestra-secrets-source
  codeLastMileKeyName: lastmile-oidc
  codeTokenSkewMilis: 60000
  publicEndpoint: false
  redirectURI:
  - https://gitlab.local.tremolo.dev/users/auth/openid_connect/callback
  signedUserInfo: false
  verifyRedirect: true

Here are the details for each option:

Option

Desription

accessTokenSkewMillis

Milliseconds milliseconds added to account for clock skew

accessTokenTimeToLive

Time an access token should live in milliseconds

authChainName

The authentication chain to use for login, do not change

clientId

The client id shared by your application

clientSecret.scretName

If using a client secret, the name of the Secret storing the client secret

clientSecret.keyName

The key in the data section of the Secret storing the client secret

codeLastMileKeyName

The name of the key used to encrypt the code token, do not change

codeTokenSkewMilis

Milliseconds to add to code token lifetime to account for clock skew

publicEndpoint

If true, a client secret is required. If false, no client secret is needed

redirectURI

List of URLs that are authorized for callback. If a URL is provided by your application that isn’t in this list SSO will fail

signedUserInfo

if true, the userinfo endpoint will return a signed JSON Web Token. If false it will return plain JSON

verifyRedirect

If true, the redirect URL provided by the client MUST be listed in the redirectURI section. Should ALLWAYS be true if not in a development environment

Once the Trust is added to the namespace, OpenUnison will pick it up automatically. You can test by trying to login to your application.

Dynamic Workflows

LDAPDynaicWorkflows

The LDAPDynaicWorkflows dynamic workflow generator lets you create a set of workflow instances dynamically from a set of groups in the virtual directory. This dynamic workflow can pull a description, approver, and name out of each group.

<dynamicConfiguration dynamic="true" className="com.tremolosecurity.provisioning.dynamicwf.LDAPDynaicWorkflows" >
  <!-- Where to start searching for groups -->
        <param name="searchBase" value="ou=dyngroups,ou=internal,ou=GenericLDAP,o=Tremolo" />
  <!-- Filter to use when searching for groups -->
        <param name="searchFilter" value="(objectClass=groupOfUniqueNames)" />
  <!-- Name of the attribute that stores the group's name, required; Exposed as groupName in the workflow -->
        <param name="groupNameAttribute" value="cn" />
  <!-- Name of the attribute that stores the approver for access to this group; Exposed as approver in the workflow -->
        <param name="approverAttribute" value="businessCategory" />
  <!-- Name of the attribute that stores a description, optional; Exposed as descriptionAttribute in the workflow -->
        <param name="descriptionAttribute" value="description" />
</dynamicConfiguration>

OpenShiftWorkflows

The OpenShiftWorkflows dynamic workflow generator will create workflows based on any list from OpenShift. Each workflow has access to the name of the item, as well as all annotations. Any annotations with a "-" or "." in the name will be replaced with a "_". For instance the annotation "openshift.io/description" would referenced in a workflow by "$openshift_io/description$".

<dynamicConfiguration dynamic="true" className="com.tremolosecurity.unison.openshiftv3.wf.OpenShiftWorkflows">
    <!-- The name of the provisioning target to use to communicate with OpenShift -->
    <param name="target" value="openshift"/>
    <!-- URI of service to use to build the workflows -->
    <param name="kind" value="/oapi/v1/projects"/>
    <!-- Filter out objects by name -->
    <param name="filter" value="default" />
    <param name="filter" value="kube-system" />
</dynamicConfiguration>

KeystoneDynamicWorkflow

The KeystoneDynamicWorkflow can generate workflows based on data inside of OpenStack. It can create workflows for either each domain or project and optionaly for each role. For instance if a workflow is based on both roles and projects then a workflow will be created for each role and each project. If there are 4 roles and 3 projects then 12 workflows will be generated. If not all targets or roles are desired (or only if certain ones are desired) then the list of targets and roles may be filtered. This is useful if you don’t want to expose certain combinations. such as the SwiftOperator or ResellerAdmin roles, for users to request.

Parameters are generated dynamicly based on the objects returned by Keystone. Each parameter starts with either a "role_", "domain_" or "project_" depending on the source object type and then the name of the attribute. For instance a project with the name "myproject" would have a parameter called "project_myproject". Note that Keystone will let you add arbitrary attributes to any role, domain or project but the openstack command line tool only supports adding properties to a project. These extra attributes can be used to define additional data such as approvers or descriptions.

<dynamicConfiguration dynamic="true" className="com.tremolosecurity.unison.openstack.KeystoneDynamicWorkflow">
    <!-- Name of the OpenStack target used to communicate with Keystone -->
    <param name="targetName" value="openstack-unison"/>
    <!-- Set to true if roles for each target should be listed, otherwise only one workflow per target is created -->
    <param name="includeRoles" value="true"/>
    <!-- Determine which target to list, may be projects or domains -->
    <param name="targetScope" value="projects"/>
    <!-- Determinse if a filter should be applied to role names, and if so how:
         none - Apply no filter
         include - Include ONLY roles listed in filterRoles
         exclude - Include roles that are NOT listed in filterRoles -->
    <param name="roleFilterMode" value="none"/>
    <!-- List of role names to filter on -->
    <param name="filterRoles" value="ResellerAdmin"/>
    <param name="filterRoles" value="SwiftOperator"/>
    <!-- Determinse if a filter should be applied to target names, and if so how:
         none - Apply no filter
         include - Include ONLY roles listed in filterTargets
         exclude - Include roles that are NOT listed in filterTargets -->
    <param name="targetFilterMode" value="none"/>
    <!-- List of target ids to filter on -->
    <param name="filterTargets" value="a315b745375e4f6bb865a291b20d9799"/>
    <param name="filterTargets" value="cda92350b719486a8b9d20e080389160"/>
</dynamicConfiguration>

DBTargetDynamicWF

Useful when generating workflows based on a database. Relies on an existing target. All fields returned by the SQL are available to the workflow -→

<dynamicConfiguration dynamic="true" className="com.tremolosecurity.provisioning.dynamicwf.DBTargetDynamicWF">
        <!-- The name of the target -->
        <param name="target" value="mydb" />
        <!-- SQL to run -->
        <param name="sql" value="SELECT id FROM x" />
</dynamicConfiguration>

Custom Mappings

This section provides documentation for custom mapping implementations.

Upn2Uid

The com.tremolosecurity.unison.freeipa.mapping.Upn2Uid mapping will take a userPrincipalName with the format user@domain and replace the "@" with a "." to make it compatible with the uid attribute.

Custom Results

This section provides documentation for custom results. To use a custom result, specify its class name in the result group as the value when choosing the "custom" option.

com.tremolosecurity.proxy.results.InjectIdToken

This custom result is designed to use an OpenID Connect session when interacting with Kubernetes through a web browser. For instance when using the dashboard this result will inject an id_token that will be validated by the api server providing authenticated access.

Dynamic Organizations Providers

com.tremolosecurity.provisioning.orgs.LoadOrgsFromK8s

Loads organizations from Org Kubernetes objects.

unison.xml Configuration
<dynamicOrgs enabled="true" className="com.tremolosecurity.provisioning.orgs.LoadOrgsFromK8s">
    <!-- OpenShiftv3 Target -->
    <params name="k8starget" value="k8s" />
    <!-- namespace to lookup objects in -->
    <params name="namespace" value="#[K8S_OPENUNISON_NS:openunison]" />
  </dynamicOrgs>
Org Example
apiVersion: openunison.tremolo.io/v1
kind: Org
metadata:
  name: cluster2
  namespace: openunison
spec:
  description: "My second cluster"
  uuid: 04901973-5f4c-46d9-9e22-55e88e168776
  parent: B158BD40-0C1B-11E3-8FFD-0800200C9A66
  showInPortal: true
  showInRequestAccess: false
  showInReports: false
  azRules:
  - scope: dn
    constraint: o=Tremolo

Option

Description

description

What appears in the blue box describing the organization

uuid

A unique ID, recommend using Type 4 UUIDs

parent

The unique id of the parent. B158BD40-0C1B-11E3-8FFD-0800200C9A66 is the root organization

showInPortal

true if should be shown on the portal page

showInRequestAccess

true if should be on the request access page

showInReports

true if should be shown in the reports screen

azRules

Who is authorized to see this badge? See https://portal.apps.tremolo.io/docs/tremolosecurity-docs/1.0.19/openunison/openunison-manual.html#_applications_applications for an explination of the authorization rules

Once added, the new organizations will be loaded dynamiclly by OpenUnison. Change the org in your PortalUrl object to match the uuid of the Org you want it to appear in.

Dynamic Portal URL Providers

com.tremolosecurity.provisioning.portal.LoadUrlsFromK8s

Load’s URLs from a PortalURL object.

unison.xml Configuration
<dynamicUrls enabled="true" className="com.tremolosecurity.provisioning.portal.LoadUrlsFromK8s">
  <!-- An OpenShiftv3 target -->
  <params name="k8starget" value="k8s" />
  <!-- The namespace to look in -->
  <params name="namespace" value="#[K8S_OPENUNISON_NS:openunison]" />

</dynamicUrls>
PortalURL Example
apiVersion: openunison.tremolo.io/v1
kind: PortalUrl
metadata:
  name: argocs
  namespace: openunison
spec:
  label: ArgoCD
  org: B158BD40-0C1B-11E3-8FFD-0800200C9A66
  url: https://ArgoCD.apps.192-168-2-140.nip.io
  icon: iVBORw0KGgoAAAANSUhEUgAAANIAAADwCAYAAAB1/Tp/AAAfQ3pUWHRSYXcgcHJvZ...
  azRules:
  - constraint: o=Tremolo
    scope: dn

Option

Descriptoin

label

The label shown on badge in the portal

org

If using orgnaizations to organize badges, the uuid of the org. If not using organizations, leave as is

url

The URL the badge should send the user to

icon

A base64 encoded icon with a width of 210 pixels and a height of 240 pixels

azRules

Who is authorized to see this badge? See https://portal.apps.tremolo.io/docs/tremolosecurity-docs/1.0.19/openunison/openunison-manual.html#_applications_applications for an explination of the authorization rules

Once added, OpenUnison will dynamiclly pick up new/changed objects.

Command Line Reference

To make it easier to configure OpenUnison a command line tool has been provided to make the job of performing certain tasks easier. This tool is a single executable jar that can be downloaded from https://www.tremolosecurity.com/nexus/service/local/repositories/releases/content/com/tremolosecurity/unison/openunison-util/1.0.17/openunison-util-1.0.17.jar.

Create AES-256 Keys

While the keytool command will create an AES-256 key in a PKCS12 keystore, this makes it easier to automate

$ java -jar openunison-util-1.0.17.jar -action create-secretkey  -unisonXMLFile openunison/src/main/webapp/WEB-INF/unison.xml -keystorePath openunison/src/main/webapp/WEB-INF/unisonKeyStore.p12 -alias session-unison

Show AES-256 Keys

The java keytool command doesn’t provide any capability to show the AES-256 keys that OpenUnison uses. In order to show the keys (such as when using TOTP) use the opeunison-util system:

$ java -jar openunison-util-1.0.17.jar -action print-secretkey  -unisonXMLFile openunison/src/main/webapp/WEB-INF/unison.xml -keystorePath openunison/src/main/webapp/WEB-INF/unisonKeyStore.p12 -alias session-unison

Export AES-256 Keys

Integrating with one of the lastmile components requires a keystore with an AES-256 key in it. Use the below command to export a keystore.

$ java -jar openunison-util-1.0.17.jar  -action export-secretkey -alias session-unison -unisonXMLFile ./config/unison.xml -keystorePath ./config/unisonKeyStore.p12 -newKeystorePath ./session-unison.p12 -newKeystorePassword '$tart123'

Import AES-256 Key

The java keytool doesn’t include any capability to import static keys. This is needed when connecting multiple OpenUnison instances.

java  -jar openunison-util-1.0.10.jar -action import-secretkey  -alias 'lastmile-portal' -secretkey '...' -unisonXMLFile ./config/unison.xml -keystorePath openunison/src/main/webapp/WEB-INF/unisonKeyStore.p12

Importing SAML2 IdP Metadata

To deploy OpenUnison as a SAML2 Service Provider / Relying Party you can manually configure the mechanism or use the util to import from the identity provider’s metadata.

$ java -jar openunison-util-1.0.17.jar -action import-sp-metadata -chainName saml2 -unisonXMLFile ./config/unison.xml -keystorePath ./config/unisonKeyStore.p12 -pathToMetaData ./idpMetadata.xml -mechanismName SAML2 -createDefault true

Generating SAML2 SP Metadata

Once the IdP’s metadata is imported, metadata from the SP/RP must be exported for import into the identity provider.

$ java -jar openunison-util-1.0.17.jar -action export-sp-metadata -chainName saml2 -unisonXMLFile ./config/unison.xml -keystorePath ./config/unisonKeyStore.p12 -pathToMetaData ./spMetadata.xml -mechanismName SAML2 -urlBase http://localhost.localdomain:8085  -createDefault true

Import SAML2 SP/RP Metadata

To import a remote service provider’s metadata use the following command. NOTE: The IdP must be created in the XML and if the trust already exists it will be overwritten.

$ java -jar openunison-util-1.0.17.jar -action import-idp-metadata -unisonXMLFile ./config/unison.xml -keystorePath ./config/unisonKeyStore.p12 -idpName MyIdP -pathToMetaData ./spmetadata.xml

Export SAML2 IdP Metadata

To export the IdP’s metadata use the following command:

$ java -jar ./openunison-util-1.0.17.jar -action export-idp-metadata -unisonXMLFile ./config/unison.xml -keystorePath ./config/unisonKeyStore.p12 -idpName MyIdP -urlBase http://localhost.localdomain:8085

Clear the Dead Letter Queue

If you are using the internal queue, or a queue system that has no process to replay the messages in the DLQ use the below command:

$ java -jar ./openunison-util-1.0.17.jar -action clear-dlq -unisonXMLFile ./config/unison.xml -keystorePath ./config/unisonKeyStore.p12 -dlqName SomeQueueName

Upgrade from 1.0.6

To upgrade OpenUnison’s configuration file from 1.0.6, run the following

$ java -jar ./openunison-util-1.0.17.jar -action upgradeFrom106 -unisonXMLFile ./config/unison.xml -keystorePath ./config/unisonKeyStore.p12

Export an Encrypted Workflow from an Approval

If you need to inspect an inflight approval’s workflow, it needs to be decrypted.

$ java -jar ./openunison-util-1.0.20.jar -action exportApprovalWorkflow  -unisonXMLFile  /home/mlbiam/git-local/openunison-k8s-activedirectory/src/main/webapp/WEB-INF/unison.xml -keystorePath /path/to/unisonKeyStore.p12 -envFile  /path-to/ou.env  -approvalId 4 -exportFile /tmp/wf.json

Appendix I - Simple Proxy Configuration

The below configuration provides a simple default configuration for the proxy. It creates a simple app on / with the username and password "test" (no quotes) and a logout.

unison.xml
<?xml version="1.0" encoding="UTF-8"?>
<tremoloConfig xmlns="http://www.tremolosecurity.com/tremoloConfig" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.tremolosecurity.com/tremoloConfig tremoloConfig.xsd">
  <!-- if you want unison to handle error pages uncomment the below -->
  <!--
    <tns:errorPage code="404" location="/auth/forms/error.jsp" />
  -->
  <applications openSessionCookieName="openSession" openSessionTimeout="9000" openSessionSecure="true" openSessionHttpOnly="true" hsts="true" hstsTTL="31536000" threadsPerRoute="6">
    <application name="LoginTest" azTimeoutMillis="30000" >
      <urls>
        <!-- The regex attribute defines if the proxyTo tag should be interpreted with a regex or not -->
        <!-- The authChain attribute should be the name of an authChain -->
        <url regex="false" authChain="formloginFilter" overrideHost="true" overrideReferer="true">
          <!-- Any number of host tags may be specified to allow for an application to work on multiple hosts.  Additionally an asterick (*) can be specified to make this URL available for ALL hosts -->
          <host>localhost.localdomain</host>
          <!-- The filterChain allows for transformations of the request such as manipulating attributes and injecting headers -->
          <filterChain>
            <filter class="com.tremolosecurity.prelude.filters.LoginTest">
              <!-- The path of the logout URI           -->
              <param name="logoutURI" value="/logout"/>
            </filter>
          </filterChain>
          <!-- The URI (aka path) of this URL -->
          <uri>/</uri>
          <!-- Tells OpenUnison how to reach the downstream application.  The ${} lets you set any request variable into the URI, but most of the time ${fullURI} is sufficient -->
          <proxyTo>http://dnm${fullURI}</proxyTo>
          <!-- List the various results that should happen -->
          <results>
            <azSuccess>
            </azSuccess>
          </results>
          <!-- Determine if the currently logged in user may access the resource.  If ANY rule succeeds, the authorization succeeds.
          The scope may be one of group, dn, filter, dynamicGroup or custom
          The constraint identifies what needs to be satisfied for the authorization to pass and is dependent on the scope:
            * group - The DN of the group in OpenUnison's virtual directory (must be an instance of groupOfUniqueNames)
            * dn - The base DN of the user or users in OpenUnison's virtual directory
            * dynamicGroup - The DN of the dynamic group in OpenUnison's virtual directory (must be an instance of groupOfUrls)
            * custom - An implementation of com.tremolosecurity.proxy.az.CustomAuthorization -->
          <azRules>
            <rule scope="dn" constraint="o=Tremolo" />
          </azRules>
        </url>
        <url regex="false" authChain="formloginFilter" overrideHost="true" overrideReferer="true">
          <!-- Any number of host tags may be specified to allow for an application to work on multiple hosts.  Additionally an asterick (*) can be specified to make this URL available for ALL hosts -->
          <host>localhost.localdomain</host>
          <!-- The filterChain allows for transformations of the request such as manipulating attributes and injecting headers -->
          <filterChain>
            <filter class="com.tremolosecurity.prelude.filters.StopProcessing" />
          </filterChain>
          <!-- The URI (aka path) of this URL -->
          <uri>/logout</uri>
          <!-- Tells OpenUnison how to reach the downstream application.  The ${} lets you set any request variable into the URI, but most of the time ${fullURI} is sufficient -->
          <proxyTo>http://dnm${fullURI}</proxyTo>
          <!-- List the various results that should happen -->
          <results>
            <azSuccess>Logout</azSuccess>
          </results>
          <!-- Determine if the currently logged in user may access the resource.  If ANY rule succeeds, the authorization succeeds.
                    The scope may be one of group, dn, filter, dynamicGroup or custom
                    The constraint identifies what needs to be satisfied for the authorization to pass and is dependent on the scope:
                      * group - The DN of the group in OpenUnison's virtual directory (must be an instance of groupOfUniqueNames)
                      * dn - The base DN of the user or users in OpenUnison's virtual directory
                      * dynamicGroup - The DN of the dynamic group in OpenUnison's virtual directory (must be an instance of groupOfUrls)
                      * custom - An implementation of com.tremolosecurity.proxy.az.CustomAuthorization -->
          <azRules>
            <rule scope="dn" constraint="o=Tremolo" />
          </azRules>
        </url>
      </urls>
      <!-- The cookie configuration determines how sessions are managed for this application -->
      <cookieConfig>
        <!-- The name of the session cookie for this application.  Applications that want SSO between them should have the same cookie name -->
        <sessionCookieName>tremolosession</sessionCookieName>
        <!-- The domain of component of the cookie -->
        <domain>localhost.localdomain</domain>
        <!-- The URL that OpenUnison will interpret as the URL to end the session -->
        <logoutURI>/logout</logoutURI>
        <!-- The name of the AES-256 key in the keystore to use to encrypt this session -->
        <keyAlias>session-unison</keyAlias>
        <!-- If set to true, the cookie's secure flag is set to true and the browser will only send this cookie over https connections -->
        <secure>false</secure>
        <!-- The number of secconds that the session should be allowed to be idle before no longer being valid -->
        <timeout>900</timeout>
        <!-- required but ignored -->
        <scope>-1</scope>
      </cookieConfig>
    </application>
  </applications>
  <myvdConfig>/Users/mlb/Documents/openunison-1.0.17/config/myvd.conf</myvdConfig>
  <authMechs>
    <mechanism name="loginForm">
      <uri>/auth/formLogin</uri>
      <className>com.tremolosecurity.proxy.auth.FormLoginAuthMech</className>
      <init>
      </init>
      <params>
        <param>FORMLOGIN_JSP</param>
      </params>
    </mechanism>
    <mechanism name="anonymous">
      <uri>/auth/anon</uri>
      <className>com.tremolosecurity.proxy.auth.AnonAuth</className>
      <init>
        <!-- The RDN of unauthenticated users -->
        <param name="userName" value="uid=Anonymous"/>
        <!-- Any number of attributes can be added to the anonymous user -->
        <param name="role" value="Users" />
      </init>
      <params>
      </params>
    </mechanism>
  </authMechs>
  <authChains>
    <!-- An anonymous authentication chain MUST be level 0 -->
    <chain name="anon" level="0">
      <authMech>
        <name>anonymous</name>
        <required>required</required>
        <params>
        </params>
      </authMech>
    </chain>
    <chain name="formloginFilter" level="1">
      <authMech>
        <name>loginForm</name>
        <required>required</required>
        <params>
          <!-- Path to the login form -->
          <param name="FORMLOGIN_JSP" value="/auth/forms/defaultForm.jsp"/>
          <!-- Either an attribute name OR an ldap filter mapping the form parameters. If this is an ldap filter, form parameters are identified by ${parameter} -->
          <param name="uidAttr" value="uid"/>
          <!-- If true, the user is determined based on an LDAP filter rather than a simple user lookup -->
          <param name="uidIsFilter" value="false"/>
        </params>
      </authMech>
    </chain>
  </authChains>
  <resultGroups>
    <!-- The name attribute is how the resultGroup is referenced in the URL -->
    <resultGroup name="Logout">
      <!-- Each result should be listed -->
      <result>
        <!-- The type of result, one of cookie, header or redirect -->
        <type>redirect</type>
        <!-- The source of the result value, one of user, static, custom -->
        <source>static</source>
        <!-- Name of the resuler (in this case a cookie) and the value -->
        <value>/auth/forms/logout.jsp</value>
      </result>
    </resultGroup>
  </resultGroups>
  <keyStorePath>/Users/mlb/Documents/openunison-1.0.17/config/unisonKeyStore.p12</keyStorePath>
  <keyStorePassword>start123</keyStorePassword>
  </tremoloConfig>
myvd.conf
#Global AuthMechConfig
server.globalChain=

server.nameSpaces=rootdse,myvdroot,testuser
server.rootdse.chain=dse
server.rootdse.nameSpace=
server.rootdse.weight=0
server.rootdse.dse.className=net.sourceforge.myvd.inserts.RootDSE
server.rootdse.dse.config.namingContexts=o=Tremolo
server.myvdroot.chain=root
server.myvdroot.nameSpace=o=Tremolo
server.myvdroot.weight=0
server.myvdroot.root.className=net.sourceforge.myvd.inserts.RootObject

server.testuser.chain=admin
server.testuser.nameSpace=ou=testuser,o=Tremolo
server.testuser.weight=0
server.testuser.admin.className=com.tremolosecurity.proxy.myvd.inserts.admin.AdminInsert
server.testuser.admin.config.uid=test
server.testuser.admin.config.password=test
unison.xml for Web Services
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<tremoloConfig xmlns="http://www.tremolosecurity.com/tremoloConfig">
  <applications openSessionCookieName="openSession" openSessionTimeout="9000" openSessionSecure="true" openSessionHttpOnly="true" hsts="true" hstsTTL="31536000">

    <application name="WebServices">
      <urls>
        <url regex="false" authChain="certClientAuth">
          <host>localhost.localdomain</host>
          <filterChain/>
          <uri>/services</uri>
          <results/>
          <azRules>
            <rule scope="dn" constraint="ou=CertAuth,ou=SSL,o=Tremolo"/>
          </azRules>
        </url>
      </urls>
      <cookieConfig>
        <sessionCookieName>tremoloWSSession</sessionCookieName>
        <domain>localhost.localdomain</domain>
        <scope>-1</scope>
        <logoutURI>/logout</logoutURI>
        <keyAlias>session-wssession</keyAlias>
        <keyPassword>
        </keyPassword>
        <secure>true</secure>
        <timeout>900</timeout>
      </cookieConfig>
    </application>

  </applications>
  <myvdConfig>/etc/openunison/myvd.conf</myvdConfig>
  <customAzRules>

  </customAzRules>
  <authMechs>
    <mechanism name="loginForm">
      <uri>/auth/formLogin</uri>
      <className>com.tremolosecurity.proxy.auth.FormLoginAuthMech</className>
      <init/>
      <params>
        <param>FORMLOGIN_JSP</param>
      </params>
    </mechanism>
    <mechanism name="anonymous">
      <uri>/auth/anon</uri>
      <className>com.tremolosecurity.proxy.auth.AnonAuth</className>
      <init>
        <param name="userName" value="uid=Anonymous"/>
        <param name="role" value="Users"/>
      </init>
      <params/>
    </mechanism>
    <mechanism name="JIT">
      <uri>/auth/jit</uri>
      <className>com.tremolosecurity.provisioning.auth.JITAuthMech</className>
      <init/>
      <params/>
    </mechanism>
    <mechanism name="certAuth">
      <uri>/auth/ssl</uri>
      <className>com.tremolosecurity.proxy.auth.CertAuth</className>
      <init>
        <param name="crl.names" value=""/>
      </init>
      <params/>
    </mechanism>
  </authMechs>
  <authChains>
    <chain name="anon" level="0">
      <authMech>
        <name>anonymous</name>
        <required>required</required>
        <params/>
      </authMech>
    </chain>

    <chain name="certClientAuth" level="40">
      <authMech>
        <name>certAuth</name>
        <required>required</required>
        <params>
          <param name="uidAttr" value="(uid=${CN})"/>
          <param name="uidIsFilter" value="true"/>
          <param name="rdnAttribute" value="CN"/>
          <param name="defaultOC" value="inetOrgPerson"/>
          <param name="dnLabel" value="CertAuth"/>
          <param name="issuer" value="CN=scale-key, OU=Scale, O=Demo, L=Demo, ST=Demo, C=Demo"/>
        </params>
      </authMech>
    </chain>
  </authChains>
  <resultGroups>
    <resultGroup name="Logout">
      <result>
        <type>redirect</type>
        <source>static</source>
        <value>/auth/forms/logout.jsp</value>
      </result>
    </resultGroup>
    <resultGroup name="FinishLogout">
      <result>
        <type>redirect</type>
        <source>static</source>
        <value>/logout</value>
      </result>
    </resultGroup>
    <resultGroup name="Invalid Login">
      <result>
        <type>redirect</type>
        <source>static</source>
        <value>/auth/forms/defaultFailedLogin.jsp</value>
      </result>
    </resultGroup>

  </resultGroups>
  <keyStorePath>/etc/openunison/unisonKeyStore.p12</keyStorePath>
  <keyStorePassword>start123</keyStorePassword>
  <provisioning>
    <targets>

    </targets>
    <workflows>

    </workflows>
    <approvalDB>
      <hibernateDialect>org.hibernate.dialect.MySQL5Dialect</hibernateDialect>
      <driver>com.mysql.jdbc.Driver</driver>
      <url>jdbc:mysql://docker2.tremolo.lan:3306/ouauditdb?useSSL=true</url>
      <user>unison</user>
      <password>start123</password>
      <maxConns>10</maxConns>
      <maxIdleConns>10</maxIdleConns>
      <!-- <hibernateProperty name="hibernate.default_schema" value="public" /> -->
      <userIdAttribute>objectguid</userIdAttribute>
      <approverAttributes>
        <value>givenName</value>
        <value>sn</value>
        <value>mail</value>
        <value>uid</value>
      </approverAttributes>
      <userAttributes>
        <value>givenName</value>
        <value>sn</value>
        <value>mail</value>
        <value>uid</value>
      </userAttributes>
      <enabled>true</enabled>
      <smtpHost>smtp.host.com</smtpHost>
      <smtpPort>587</smtpPort>
      <smtpUser>donotreply@domain</smtpUser>
      <smtpPassword>********</smtpPassword>
      <smtpSubject>Awaiting Approvals</smtpSubject>
      <smtpFrom>donotreply@mydomain.com</smtpFrom>
      <smtpTLS>true</smtpTLS>
      <encryptionKey>session-workflows</encryptionKey>
      <smtpUseSOCKSProxy>false</smtpUseSOCKSProxy>
      <smtpSOCKSProxyHost>
      </smtpSOCKSProxyHost>
      <smtpSOCKSProxyPort>0</smtpSOCKSProxyPort>
      <smtpLocalhost>
      </smtpLocalhost>
      <validationQuery>SELECT 1</validationQuery>
    </approvalDB>
    <org name="MyOrg" description="MyOrg Enterprise Applications" uuid="687da09f-8ec1-48ac-b035-f2f182b9bd1e">

    </org>
    <queueConfig isUseInternalQueue="true" maxProducers="5"  maxConsumers="5" taskQueueName="TremoloUnisonTaskQueue" smtpQueueName="TremoloUnisonSMTPQueue" encryptionKeyName="session-queues">

    </queueConfig>
    <portal>

    </portal>
    <scheduler useDB="false" threadCount="3" instanceLabel="testing" instanceIPMask="127"/>
    <listeners/>
    <reports>

    </reports>
  </provisioning>
  </tremoloConfig>

Appendix II - Deploying OpenUnison on Tomcat 8

OpenUnison is a simple J2EE application that can be deployed in one of two ways:

  1. Download the pre-built WAR from Tremolo Security’s Nexus repository and deploy it to a J2EE container.

  2. Use Apache Maven to build a web application using the OpenUnison in Tremolo Security’s Nexus repository as a starting point. Deploy the resulting WAR file to a J2EE container.

In either case, a J2EE container is required. Tomcat 8 is tested but any Servlet 3.x container should work. Further sections of this document assume Apache Tomcat 8 is used.

Deploying the Pre-Built WAR

If you are unfamiliar with Maven or wish to deploy OpenUnison using the pre-built WAR file, follow these steps:

1. Download the appropriate pre-built OpenUnison WAR file.

mkdir workingdirectory cd workingdirectory unzip ../open-unison-webapp[-webservices]-$VERSION.war .3. Update the configuration based on this document .4. Repackage the WAR file

$ cd  open-unison-webapp[-webservices]-$VERSION
$ zip -R open-unison-webapp[-webservices]-$VERSION.war *
5. Rename as ROOT.war
$ mv open-unison-webapp[-webservices]-$VERSION.war ROOT.war
6. Delete all of the default files/directories in $TOMCAT_HOME/webapps (where $TOMCAT_HOME is the root directory of the Tomcat installation)

rm -rf $TOMCAT_HOME/webapps/*

7. Copy the ROOT.war file to $TOMCAT_HOME/webapps

cp ./openunison.war $TOMCAT_HOME/webapps/ROOT.war

8. Start Tomcat

$TOMCAT_HOME/bin/startup.sh

The main downside to this approach is that new OpenUnison modules will be more difficult to integrate since you won’t have any mechanism for dependency management. If you are unfamiliar with Maven however, this is a good way to start.

Deploying with Apache Maven

OpenUnison is packaged as a Maven web application that can be customized using Maven’s overlay plugin. To do so, follow the steps below:

1. Create a simple Maven project
$ mvn archetype:generate -DgroupId=com.mycompany.openunison -DartifactId=openunison -DinteractiveMode=false -DarchetypeArtifactId=maven-archetype-webapp
$ rm openunison/src/main/webapp/index.jsp
$ rm openunison/src/main/webapp/WEB-INF/web.xml
2. Configure the pom.xml file.

Add the following text to the OpenUnison pom.xml file to to add the Tremolo Security repository configuration:

<repositories>
  <repository>
    <id>Tremolo Security</id>
    <url>https://www.tremolosecurity.com/nexus/content/repositories/releases/</url>
  </repository>
</repositories>

Add the following OpenUnison dependencies to the pom.xml file in the "dependencies" section:

<dependencies>
  <dependency>
    <groupId>com.tremolosecurity.unison</groupId>
    <artifactId>open-unison-webapp</artifactId>
    <version>1.0.17</version>
    <type>war</type>
    <scope>runtime</scope>
  </dependency>
  <dependency>
    <groupId>com.tremolosecurity.unison</groupId>
    <artifactId>open-unison-webapp</artifactId>
    <version>1.0.17</version>
    <type>pom</type>
  </dependency>
</dependencies>

Add the following overlay plugin configuration to the pom.xml file.

<build>
  <plugins>
    <plugin>
      <artifactId>maven-compiler-plugin</artifactId>
      <version>3.1</version>
      <configuration>
        <source>1.7</source>
        <target>1.7</target>
      </configuration>
    </plugin>
    <plugin>
      <groupId>org.apache.maven.plugins</groupId>
      <artifactId>maven-war-plugin</artifactId>
      <version>2.6</version>
      <configuration>
        <overlays>
          <overlay>
            <groupId>com.tremolosecurity.unison</groupId>
            <artifactId>open-unison-webapp</artifactId>
          </overlay>
        </overlays>
      </configuration>
    </plugin>
  </plugins>
</build>
3. Run the package artifact to build the war file in the project’s target directory

mvn package

4. Deploy the OpenUnison WAR file

Remove all of the files and directories from $TOMCAT_HOME/webapps (where $TOMCAT_HOME is the Tomcat root installation directory) and copy the openunison.war file to $TOMCAT_HOME/webapps/ROOT.war.

rm -r $TOMCAT_HOME/webapps/* cp target/openunison.war $TOMCAT_HOME/webapps/ROOT.war

5. Start tomcat.

$TOMCAT_HOME/bin/startup.sh

OpenUnison Configuration Files

There are six files important to a customized configuration of OpenUnison. They are:

  1. context.xml - Tells OpenUnison where to find configuration files

  2. unisonService.props - Tells OpenUnison what ports to use and to translate

  3. unison.xml - The main OpenUnison configuration file

  4. unisonKeyStore.p12 - The OpenUnison keystore

  5. myvd.conf - The MyVirtualDirectory configuration file

  6. log4j2.xml - The logging configuration file

The configuration files may be stored and managed within the WEB-INF directory in the OpenUnison web application, or they may be stored and managed in a separate directory. The latter option (storing the files in a separate configuration directory) is generally the best option. The configuration directory should be owned by the same user that will run Tomcat. The remainder of this document assumes that the configuration files are stored and managed in a separate configuration directory at /etc/openunison.

Managing Configuration Files and Parameterization Across Environments

A complex OpenUnison deployment can quickly create a large unison.xml file or myvd.conf. To make it easier to manage OpenUnison has two features:

  1. Environment variables and system properties can be referenced by placing them between #[]. For instance, to refer to the value held in an environment variable called "MYSQL_HOST" use #[MYSQL_HOST]

  2. Files can be included using @[] in the form:

    1. @[dir:directory-name] where directory-name is the name of a directory relative to the configuration file. This will import all files in the directory, in alphabetical order.

    2. @[file:directory/filename.xml] where directory/filename.xml is the path to a specific file relative to the configuration file.

A file containing a list of system properties to import and be specified via the unisonEnvironmentFile jvm option can be used to externalize configuration properties. To specify the properties in /etc/ou.env you would add -DunisonEnvironmentFile=/etc/out.env to your application server’s startup process.

context.xml

In the context of OpenUnison, Tomcat’s context.xml file is used to define environment variables and their values. The file is stored in a deployed web application’s META-INF directory. If the directory and file don’t exist in your OpenUnison directory ($OPENUNISON_HOME) it must be created. If using another servlet container these environment variables need to be set via that server’s mechanism.

The following may be used as the full content of the __$OPENUNISON_HOME__/META-INF/context.xml file. It assumes that all configuration files are stored in /etc/openunison.

META-INF/context.xml
<Context>
  <Environment name="unisonConfigPath" value="/etc/openunison/unison.xml" type="java.lang.String"/>
  <Environment name="unisonServiceConfigPath" value="/etc/openunison/unisonService.props" type="java.lang.String"/>
</Context>

unisonService.props

The unisonService.props file is used to configure internal and external ports in OpenUnison. OpenUnison uses this configuration to determine the ports on which to listen and whether to do port translation.

Since Tomcat usually isn’t run as a privileged user, it is usually configured to listen on port(s) 8080 and/or 8443. Port translation may be used to direct users to port(s) 80 (http) and/or 443 (https), without requiring Tomcat to be run as a privileged user. Port translation may also be used to redirect all requests to secure port(s).

The unisonService.props file should be stored in the configuration directory named in the context.xml file. The unisonServiceConfigPath entry defines the location of the file (see the sample context.xml file above).

Example unisonService.props
# Redirect from http to https
com.tremolosecurity.openunison.forceToSSL=true

# The port on which Tomcat is configured to listen for http requests
com.tremolosecurity.openunison.openPort=8080

# The port on which Tomcat is configured to listen for https requests
com.tremolosecurity.openunison.securePort=8443

# The external port on which OpenUnison should listen for incoming requests
com.tremolosecurity.openunison.externalOpenPort=80

# The secured/encrypted external port on which OpenUnison should listen for incoming requests
com.tremolosecurity.openunison.externalSecurePort=443

#Uncomment and set for production deployments
#com.tremolosecurity.openunison.activemqdir=/var/lib/unison-activemq

Logging

OpenUnison uses the Log4j2 framework - http://logging.apache.org/log4j/2.x/ . By default OpenUnison is configured to send all output to standard out. If a different configuraiton is desired, add a log4j2.xml file in the src/main/webapp/WEB-INF/classes directory in the maven project to overload it.

unison.xml

The unison.xml file contains most of the OpenUnison configuration options. This file is based on an annotated XML schema file. While not required, it is recommended that the schema file be used when creating the unison.xml file. If using an editor such as Eclipse the schema file will make it much easier to navigate the configuration options. The easiest way to get this file is to copy it from the build in your target directory.

$ cp target/war/work/com.tremolosecurity.unison/open-unison-webapp/WEB-INF/tremoloConfig.xsd src/main/webapp/WEB-INF/

Once the file is copied, you can create src/main/webapp/WEB-INF/unison.xml file from the schema file using any schema aware XML editor or using the examples in this guide. At a minimum you must set the myvdConfig, keyStorePath and keyStorePassword elements:

All of the elements are described in this document.

<?xml version="1.0" encoding="UTF-8"?>
  <tremoloConfig xmlns="http://www.tremolosecurity.com/tremoloConfig" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.tremolosecurity.com/tremoloConfig tremoloConfig.xsd ">
  <applications openSessionCookieName="openSession" openSessionTimeout="9000" openSessionSecure="true" openSessionHttpOnly="true" hsts="true" hstsTTL="31536000"/>
  <myvdConfig>WEB-INF/myvd.conf</myvdConfig>
  <authMechs/>
  <authChains/>
  <resultGroups/>
  <keyStorePath>WEB-INF/unisonKeyStore.p12</keyStorePath>
  <keyStorePassword>password</keyStorePassword>
</tremoloConfig>

NOTE - This sample unison.xml file will allow OpenUnison to start, but not to do anything useful. Refer to the [Configuration Reference] section for a detailed description of all configuration options. Refer to the [Appendix I - Simple Proxy Configuration] in Appendix I for a simple configuration that can be used as a starting point. Be careful to replace the instances of "localhost.localdomain" with the appropriate hostname and update the keystore password appropriately. See the next section for information about creating the Java keystore.

Unison Keystore

All certificates and keys used by OpenUnison are stored in a Java key store. The name of the keystore file is configured in the unison.xml file (see the unison.xml section above). There are two requirements for the keystore:

  1. It must be a Java Extended Key Store (PKCS12)

  2. All key passwords must match the keystore password

OpenUnison assumes the use of TLS authentication for all web services so a TLS certificate MUST be created. The sample command below can be used to create a secret (AES256) key to be used for session OpenUnison creates and store it in a PKCS12 keystore called unisonKeyStore.p12 (in src/main/webapp/WEB-INF)

$ keytool -genseckey -alias session-unison -keyalg AES -keysize 256 -storetype PKCS12 -keystore src/main/webapp/WEB-INF/unisonKeyStore.p12
Enter keystore password:
Re-enter new password:
Enter key password for <session-unison>
 (RETURN if same as keystore password):

The command below can be used to create a TLS certificate.

$ keytool -genkeypair -storetype PKCS12 -alias unison-tls -keyalg RSA -keysize 2048 -sigalg SHA256withRSA -keystore src/main/webapp/WEB-INF/unisonKeyStore.p12
Enter keystore password:
What is your first and last name?
  .
  .
  .
Note
When deploying into a container such as Tomcat or JBoss it is recommended to configure the container and OpenUnison use the same keystore.

myvd.conf

OpenUnison uses an embedded version of MyVirtualDirectory to work with user data. This allows OpenUnison to work with LDAP directories, databases, Active Directory, web services, etc. It also provides a powerful mechanism for manipulating identity data. For example, using MyVirtualDirectory, OpenUnison can be configured to append additional attributes to existing AD entries without affecting AD itself.

There are MyVirtualDirectory two directory roots that should be used:

  • ou=Tremolo - This is where OpenUnison will look for identity data

  • ou=Data - This is where directories may be configured that OpenUnison won’t look for identity data

The ou=Data root is useful for configuring joins where the directories being joined are configured under ou=Data and the joiner is configured under ou=Tremolo.

Create a text file called src/main/webapp/WEB-INF/myvd.conf with the following contents:

#Global AuthMechConfig
server.globalChain=

server.nameSpaces=rootdse,myvdroot
server.rootdse.chain=dse
server.rootdse.nameSpace=
server.rootdse.weight=0
server.rootdse.dse.className=net.sourceforge.myvd.inserts.RootDSE
server.rootdse.dse.config.namingContexts=o=Tremolo
server.myvdroot.chain=root
server.myvdroot.nameSpace=o=Tremolo
server.myvdroot.weight=0
server.myvdroot.root.className=net.sourceforge.myvd.inserts.RootObject

See MyVirtualDirectory’s website (http://myvd.sourceforge.net/) for more information on how to configure MyVirtualDirectory.

Deploy Web Services

OpenUnison’s web services provide a powerful way for applications, such as Scale, to make identity management requests. OpenUnison’s web services are built directly into OpenUnison and can be run long side or part of the reverse proxy. The only differences when creating an OpenUnison web services host from the revese proxy are:

  1. Use the open-unison-webapp-webservices-1.0.17.war (either as open-unison-webapp as a maven dependency to the war file), this includes additional servlet configurations in the web.xml file

  2. Make sure to configure tomcat to use TLS and "want" authentication. In tomcat’s server.xml find the Connector on port 8443, uncomment it and update to look like:

<Connector port="8443" protocol="org.apache.coyote.http11.Http11NioProtocol"
              maxThreads="150" SSLEnabled="true" scheme="https" secure="true"
              clientAuth="want" sslProtocol="TLS" keystoreFile="/etc/openunison/unisonKeyStore.p12" keystoreType="PKCS12" keystorePass="mypassword" keyAlias="unison-tls"
              truststoreFile="/etc/openunison/unisonKeyStore.p12" truststoreType="PKCS12"/>

Finally, configure OpenUnison to protect the web services with a certificate authentication chain. Appendix I was a basic configuration template for a web services host. In order to use the template, you will need to create additional keys in the OpenUnison keystore:

$ keytool -genseckey -alias session-wssession -keyalg AES -keysize 256 -storetype PKCS12 -keystore /etc/openunison/unisonKeyStore.p12
Enter keystore password:
Re-enter new password:
Enter key password for <session-unison>
 (RETURN if same as keystore password):

$ keytool -genseckey -alias session-workflows -keyalg AES -keysize 256 -storetype PKCS12 -keystore /etc/openunison/unisonKeyStore.p12
Enter keystore password:
Re-enter new password:
Enter key password for <session-unison>
  (RETURN if same as keystore password):

$ keytool -genseckey -alias session-queues -keyalg AES -keysize 256 -storetype PKCS12 -keystore /etc/openunison/unisonKeyStore.p12
Enter keystore password:
Re-enter new password:
Enter key password for <session-unison>
 (RETURN if same as keystore password):