Kubernetes Identity Management – Part I : SSO

Marc BoorshteinUncategorized

Build an SSO Solution for Kubernetes

After we finished integration for OpenShift into Unison, we wanted to move on to Kubernetes.  We knew that the RBAC model in Kubernetes was based off of OpenShift’s so we figured supporting Kubernetes would be pretty straight forward.  It turns out we made some false assumptions.  The main difference between Kubernetes and OpenShift is that OpenShift has an internal model for storing users and groups in addition to assigning them to roles but Kubernetes has no such internal model.  There is no persistent idea of a user or group in Kubernetes.  The only way to assign a user or group is to use a tokens file, which needs to be reloaded on each change, or to use SSO to assert a user and groups whenever calling a service.  We went the SSO route.  We had a couple of key goals:

  1. Users must be able to login using a web browser to support multiple authentication mechanisms (ie multi-factor, certificates, etc)
  2. Once logged in, users needed to be able to retrieve their access token for use with the CLI
  3. The dashboard must be accessible through OpenUnison’s reverse proxy
  4. OpenUnison will be able to dynamically create workflows based on annotations in kubernetes
  5. OpenUnison must be able to provision access to resources WITHOUT adding data to Active Directory

We’ll cover 4 & 5 in a future blog.

oidc_login (1)

So how does it work?

  1. First the user will login to OpenUnison and ScaleJS
  2. OpenUnison will redirect the user to KeyCloak which will authenticate the user against MyVirtualDirectory
  3. KeyCloak will redirect the user back to OpenUnison with an access token where OpenUnison will retrieve the JWT from KeyCloak and load ScaleJS including a portal link to the Kubernetes dashboard
  4.  When the user clicks on the link their access token is injected into the request to the API server
  5. The API server validates the request from OpenUnison against KeyCloak and retrieves a JWT with the user’s id and groups
  6. If the user wishes to use kubectl, the user will click on the link for the OAuth2 Key in ScaleJS
  7. Finally use that key as the –token with kubectl

Seeing is believing:

Kubernetes Dashboard from Tremolo Security Inc. on Vimeo.

 

At Red Hat Summit we had a very successful demo around a common enterprise problem: users and their passwords are in Active Directory but getting an administrative account to create groups and add users to them wouldn’t work.  So we went with the same model as our OpenShift demo.  An ActiveDirectory forest for users, and FreeIPA for groups and additional attributes.  The accounts in FreeIPA and Active Directory are joined in real time by MyVirtualDirectory.  We could have used OpenUnison’s internal virtual directory to join the two directories, but decided to use an external virtual directory so the same data could be shared between OpenUnison and an OpenID Connect identity provider (IdP).  We have written a VERY basic OpenID Connect identity provider for OpenUnison, but it didn’t fulfill enough of the spec to work with Kubernetes so we decided to do our POC with KeyCloak.

Setting Up MyVirtualDirectory

MyVirtualDirectory has many great features.  My favorite is its joiner, able to take account attributes from two directories and make them look and act like a single object.  No more wrestling with Active Directory admins to add to the schema or create groups or permissions.  All we needed was a simple no permissions read-only service account.  Since I already had the OpenShift demo’s setup available, I just piggy-backed off of that:

server.listener.port=10983
#Configure global chains
server.globalChain=
server.nameSpaces=Root,myvdroot,ent2k12-domain-com,ipa.rheldemo.lan,enterprise,enterprise-groups

#Define RootDSE
server.Root.chain=RootDSE
server.Root.nameSpace=
server.Root.weight=0
server.Root.RootDSE.className=net.sourceforge.myvd.inserts.RootDSE
server.Root.RootDSE.config.namingContexts=dc=domain,dc=com|ou=ent2k12-domain-com,o=Data|ou=ipa.rheldemo.lan,o=Data
server.Root.RootDSE.config.supportedControls=2.16.840.1.113730.3.4.18,2.16.840.1.113730.3.4.2,1.3.6.1.4.1.4203.1.10.1,1.2.840.113556.1.4.319,1.2.826.0.1.334810.2.3,1.2.826.0.1.3344810.2.3,1.3.6.1.1.13.2,1.3.6.1.1.13.1,1.3.6.1.1.12
server.Root.RootDSE.config.supportedSaslMechanisms=NONE

server.myvdroot.chain=root
server.myvdroot.nameSpace=dc=domain,dc=com
server.myvdroot.weight=0
server.myvdroot.root.className=net.sourceforge.myvd.inserts.RootObject

server.ent2k12-domain-com.chain=mapobjectguid,objectguid2text,dnmapper,objmap,membertrans,ldap
server.ent2k12-domain-com.nameSpace=ou=ent2k12-domain-com,o=Data
server.ent2k12-domain-com.weight=0
server.ent2k12-domain-com.enabled=true
server.ent2k12-domain-com.mapobjectguid.className=net.sourceforge.myvd.inserts.mapping.AttributeMapper
server.ent2k12-domain-com.mapobjectguid.config.mapping=adObjectGUID=objectGUID,adUid=uid
server.ent2k12-domain-com.objectguid2text.className=com.tremolosecurity.proxy.myvd.inserts.util.UUIDtoText
server.ent2k12-domain-com.objectguid2text.config.attributeName=objectGUID
server.ent2k12-domain-com.dnmapper.className=net.sourceforge.myvd.inserts.mapping.DNAttributeMapper
server.ent2k12-domain-com.dnmapper.config.dnAttribs=member,owner,member,memberOf,distinguishedName,manager
server.ent2k12-domain-com.dnmapper.config.localBase=ou=ent2k12-domain-com,o=Data
server.ent2k12-domain-com.dnmapper.config.urlAttribs=
server.ent2k12-domain-com.objmap.className=net.sourceforge.myvd.inserts.mapping.AttributeValueMapper
server.ent2k12-domain-com.objmap.config.mapping=objectClass.inetOrgPerson=user,objectClass.groupOfNames=group
server.ent2k12-domain-com.membertrans.className=net.sourceforge.myvd.inserts.mapping.AttributeMapper
server.ent2k12-domain-com.membertrans.config.mapping=member=member,uid=samAccountName
server.ent2k12-domain-com.dnmapper.config.remoteBase=cn=Users,dc=ent2k12,dc=domain,dc=com
server.ent2k12-domain-com.ldap.className=net.sourceforge.myvd.inserts.ldap.LDAPInterceptor
server.ent2k12-domain-com.ldap.config.host=w2k12.tremolo.lan
server.ent2k12-domain-com.ldap.config.port=389
server.ent2k12-domain-com.ldap.config.remoteBase=cn=Users,dc=ent2k12,dc=domain,dc=com
server.ent2k12-domain-com.ldap.config.proxyDN=cn=Administrator,cn=Users,dc=ent2k12,dc=domain,dc=com
server.ent2k12-domain-com.ldap.config.proxyPass=xxxxx
server.ent2k12-domain-com.ldap.config.ignoreRefs=true
server.ent2k12-domain-com.ldap.config.passBindOnly=true
server.ent2k12-domain-com.ldap.config.maxIdle=90000
server.ent2k12-domain-com.ldap.config.maxMillis=90000
server.ent2k12-domain-com.ldap.config.maxStaleTimeMillis=90000
server.ent2k12-domain-com.ldap.config.minimumConnections=10
server.ent2k12-domain-com.ldap.config.maximumConnections=10
server.ent2k12-domain-com.ldap.config.usePaging=false
server.ent2k12-domain-com.ldap.config.pageSize=0
server.ent2k12-domain-com.ldap.config.heartbeatIntervalMillis=90000

server.ipa.rheldemo.lan.chain=debugipa,carlicense2adObjectGUID,mapowner,dnmapper,ldap
server.ipa.rheldemo.lan.nameSpace=ou=ipa.rheldemo.lan,o=Data
server.ipa.rheldemo.lan.weight=0
server.ipa.rheldemo.lan.enabled=true
server.ipa.rheldemo.lan.debugipa.className=net.sourceforge.myvd.inserts.DumpTransaction
server.ipa.rheldemo.lan.debugipa.config.logLevel=debug
server.ipa.rheldemo.lan.carlicense2adObjectGUID.className=net.sourceforge.myvd.inserts.mapping.AttributeMapper
server.ipa.rheldemo.lan.carlicense2adObjectGUID.config.mapping=adObjectGUID=carLicense
server.ipa.rheldemo.lan.mapowner.className=net.sourceforge.myvd.inserts.mapping.DNAttributeMapper
server.ipa.rheldemo.lan.mapowner.config.dnAttribs=owner
server.ipa.rheldemo.lan.mapowner.config.localBase=ou=ipa.rheldemo.lan,o=Data
server.ipa.rheldemo.lan.mapowner.config.remoteBase=cn=accounts,dc=rheldemo,dc=lan
server.ipa.rheldemo.lan.dnmapper.className=net.sourceforge.myvd.inserts.mapping.DNAttributeMapper
server.ipa.rheldemo.lan.dnmapper.config.dnAttribs=member,owner,member,memberOf,distinguishedName,manager
server.ipa.rheldemo.lan.dnmapper.config.localBase=ou=ipa.rheldemo.lan,o=Data
server.ipa.rheldemo.lan.dnmapper.config.urlAttribs=memberurl
server.ipa.rheldemo.lan.dnmapper.config.remoteBase=cn=users,cn=accounts,dc=rheldemo,dc=lan
server.ipa.rheldemo.lan.ldap.className=net.sourceforge.myvd.inserts.ldap.LDAPInterceptor
server.ipa.rheldemo.lan.ldap.config.host=ipa.rheldemo.lan
server.ipa.rheldemo.lan.ldap.config.port=389
server.ipa.rheldemo.lan.ldap.config.remoteBase=cn=users,cn=accounts,dc=rheldemo,dc=lan
server.ipa.rheldemo.lan.ldap.config.proxyDN=cn=Directory Manager
server.ipa.rheldemo.lan.ldap.config.proxyPass=xxxxxx
server.ipa.rheldemo.lan.ldap.config.ignoreRefs=true
server.ipa.rheldemo.lan.ldap.config.passBindOnly=true
server.ipa.rheldemo.lan.ldap.config.maxMillis=90000
server.ipa.rheldemo.lan.ldap.config.maxStaleTimeMillis=90000
server.ipa.rheldemo.lan.ldap.config.minimumConnections=10
server.ipa.rheldemo.lan.ldap.config.maximumConnections=10
server.ipa.rheldemo.lan.ldap.config.usePaging=false
server.ipa.rheldemo.lan.ldap.config.pageSize=0
server.ipa.rheldemo.lan.ldap.config.maxIdle=90000
server.ipa.rheldemo.lan.ldap.config.heartbeatIntervalMillis=90000

server.enterprise.chain=cleanSearchAttrs,mapJoinedDNs,mapUSerJoinDNs,joinedOnly,joiner
server.enterprise.nameSpace=ou=enterprise,dc=domain,dc=com
server.enterprise.weight=0
server.enterprise.enabled=true
server.enterprise.cleanSearchAttrs.className=net.sourceforge.myvd.inserts.mapping.AttributeCleaner
server.enterprise.cleanSearchAttrs.config.clearAttributes=true
server.enterprise.mapJoinedDNs.className=net.sourceforge.myvd.inserts.mapping.DNAttributeMapper
server.enterprise.mapJoinedDNs.config.dnAttribs=member,owner
server.enterprise.mapJoinedDNs.config.localBase=ou=enterprise-groups,dc=domain,dc=com
server.enterprise.mapJoinedDNs.config.remoteBase=cn=groups,cn=accounts,dc=rheldemo,dc=lan
server.enterprise.mapUSerJoinDNs.className=net.sourceforge.myvd.inserts.mapping.DNAttributeMapper
server.enterprise.mapUSerJoinDNs.config.dnAttribs=memberOf
server.enterprise.mapUSerJoinDNs.config.localBase=ou=enterprise-groups,dc=domain,dc=com
server.enterprise.mapUSerJoinDNs.config.remoteBase=cn=groups,cn=accounts,dc=rheldemo,dc=lan
server.enterprise.joinedOnly.className=net.sourceforge.myvd.inserts.mapping.EntryFilter
server.enterprise.joinedOnly.config.filter=(joinedDNs=*)
server.enterprise.joinedOnly.config.objectClass=inetOrgPerson
server.enterprise.joiner.className=net.sourceforge.myvd.inserts.join.Joiner
server.enterprise.joiner.config.primaryNamespace=ou=ipa.rheldemo.lan,o=Data
server.enterprise.joiner.config.joinedNamespace=ou=ent2k12-domain-com,o=Data
server.enterprise.joiner.config.joinFilter=(adObjectGUID=ATTR.adObjectGUID)
server.enterprise.joiner.config.joinedAttributes=adObjectGUID,givenName,sn,displayName,adUID,userPrincipalName,mobile
server.enterprise.joiner.config.joinedObjectClasses=inetOrgPerson
server.enterprise.joiner.config.bindPrimaryFirst=false

server.enterprise-groups.chain=mapServiceAccounts,mapmembers,mapmember,dnmapper,ldap
server.enterprise-groups.nameSpace=ou=enterprise-groups,dc=domain,dc=com
server.enterprise-groups.weight=0
server.enterprise-groups.enabled=true
server.enterprise-groups.mapServiceAccounts.className=net.sourceforge.myvd.inserts.mapping.DNAttributeMapper
server.enterprise-groups.mapServiceAccounts.config.dnAttribs=member
server.enterprise-groups.mapServiceAccounts.config.localBase=ou=enterprise-serviceaccounts,dc=domain,dc=com
server.enterprise-groups.mapServiceAccounts.config.remoteBase=ou=ipa.rheldemo.lan,o=Data
server.enterprise-groups.mapmembers.className=net.sourceforge.myvd.inserts.join.JoinSearchMapDNAttribute
server.enterprise-groups.mapmembers.config.out2inSearchRoot=ou=ipa.rheldemo.lan,o=Data
server.enterprise-groups.mapmembers.config.dnAttributes=member
server.enterprise-groups.mapmembers.config.in2outSearchRoot=dc=domain,dc=com
server.enterprise-groups.mapmembers.config.searchFilter=(adObjectGUID=#)
server.enterprise-groups.mapmembers.config.joinAttribute=adObjectGUID
server.enterprise-groups.mapmember.className=net.sourceforge.myvd.inserts.mapping.DNAttributeMapper
server.enterprise-groups.mapmember.config.dnAttribs=member
server.enterprise-groups.mapmember.config.localBase=ou=ipa.rheldemo.lan,o=Data
server.enterprise-groups.mapmember.config.remoteBase=cn=users,cn=accounts,dc=rheldemo,dc=lan
server.enterprise-groups.dnmapper.className=net.sourceforge.myvd.inserts.mapping.DNAttributeMapper
server.enterprise-groups.dnmapper.config.dnAttribs=member,owner,member,memberOf,distinguishedName,manager
server.enterprise-groups.dnmapper.config.localBase=ou=enterprise-groups,dc=domain,dc=com
server.enterprise-groups.dnmapper.config.urlAttribs=memberurl
server.enterprise-groups.dnmapper.config.remoteBase=cn=groups,cn=accounts,dc=rheldemo,dc=lan
server.enterprise-groups.ldap.className=net.sourceforge.myvd.inserts.ldap.LDAPInterceptor
server.enterprise-groups.ldap.config.host=ipa.rheldemo.lan
server.enterprise-groups.ldap.config.port=389
server.enterprise-groups.ldap.config.remoteBase=cn=groups,cn=accounts,dc=rheldemo,dc=lan
server.enterprise-groups.ldap.config.proxyDN=cn=Directory Manager
server.enterprise-groups.ldap.config.proxyPass=xxxxx
server.enterprise-groups.ldap.config.ignoreRefs=true
server.enterprise-groups.ldap.config.passBindOnly=true
server.enterprise-groups.ldap.config.maxMillis=90000
server.enterprise-groups.ldap.config.maxStaleTimeMillis=90000
server.enterprise-groups.ldap.config.minimumConnections=10
server.enterprise-groups.ldap.config.maximumConnections=10
server.enterprise-groups.ldap.config.usePaging=false
server.enterprise-groups.ldap.config.pageSize=0
server.enterprise-groups.ldap.config.maxIdle=90000
server.enterprise-groups.ldap.config.heartbeatIntervalMillis=90000

The first two namespaces are to connect to FreeIPA and Active Directory respectively and then next create the join.  Since we wanted to re-use our FreeIPA groups I added another block to properly map those.  So if someone looks at this directory in a directory browser, it’ll look just like a normal ldap directory.  You would never know that the attributes were coming from two different directories.

Deploying KeyCloak

This part was hard, i mean really hard, i mean crazy hard.  It took about 5 minutes.  I downloaded the latest from the website (2.1?) got it up and running and created a realm for kubernetes (that was about 2 minutes).  Then I added an LDAP Federation with my virtual directory (another minute).  Then I added a custom claim to represent the groups we’re sending to kubernetes (another minute).  Finally, I setup a client for OpenUnison.

The only “trick” I ran into had nothing to do with KC.  Kubernetes will ONLY talk over TLS when doing OpenID Connect, so I setup a self signed certificate in Wildfly.  It turns out Kubernetes won’t work with a certificate that signs its self, only with a certificate thats signed by a CA (doesn’t need to be a commercial CA, just a CA).  The CA its self can be self signed and explicitly trusted by Kubernetes.  This is an issue with Golang’s TLS implementation, as seen in this ticket on GitHub. So I used the script that was referenced in the ticket to create a CA and cert, imported them into a java keystore by first converting them to PKCS12 and we were good to go.

Deploying Kubernetes

This was the single hardest part of this endeavor.  Not because Kubernetes is so hard to deploy but because all of the “distros” were either VERY hard to customize or impossible to.  In order to configure Kubernetes’ OIDC support you need to add parameters to the startup of the API server but most of the quick starts and distros hid this in a pre-built binary or docker image.  I made some progress with kube-deploy’s multi node docker project but that was still very hard to work with (for details, see the issue I opened).  Special thanks to Eric Chiang from CoreOS for pointing me to their single node vagrant image.  Fit the bill perfectly.  I was able to edit a YAML file, restart docker and we were good to go.

Deploying OpenUnison

OK, so for the main event.  First thing I needed to do was get SSO working with KeyCloak.  We had already built an OpenID Connect authentication mechanism so that took a couple of minutes to get working.  Once we had SSO we wanted to use ScaleJS to give users an access portal where they could view their token and request access for roles in Kubernetes once we got to the identity management portion of the program.  We also wanted an easy way to get access to the Kubernetes dashboard.  First we deployed ScaleJS’ main application which is the primary interface for access requests, links, reports, approvals, etc.  Since we had our identity repository in an external virtual directory and sso with KeyCloak this was very easy.

Next we needed to setup ScaleJS Token.  This is an application that runs in OpenUnison meant for getting tokens to users.  Its got a plugin model for different token types and since the OpenID Connect authentication mechanism stores the token in the session I created a new plugin to retrieve that token.  I then added a portal URL and the image of a key so users could retrieve the token for use in kubectl.

At this point we were able to login to KeyCloak, get the token and use it to access kubectl.  Now we needed to integrate the Kubernetes dashboard.  This was pretty easy.  I setup a URL in OpenUnison pointing to the API server running on 443.  I needed a way to send the user’s access token to Kubernetes so I created a custom response that would create a string with the word “bearer” and a space in front of the access token and then created a response group creating an Authorization header using the custom response.  Worked perfectly.  Finally, I created a URL for the dashboard with the Kubernetes logo.

Here’s the OpenUnison configuration:

<?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” ldapRoot=”dc=domain,dc=com” groupObjectClass=”groupOfNames” groupMemberAttribute=”member” userObjectClass=”inetOrgPerson”>
<applications openSessionCookieName=”openSession” openSessionTimeout=”9000″>
<application name=”keycloak” 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=”anon” overrideHost=”false” overrideReferer=”false”>
<host>kcdev.tremolosecurity.com</host>
<filterChain>
</filterChain>
<uri>/</uri>
<proxyTo>http://localhost:8080${fullURI}</proxyTo>
<results>
<azSuccess>
</azSuccess>
</results>
<azRules>
<rule scope=”filter” constraint=”(objectClass=*)” />
</azRules>
</url>
</urls>
<cookieConfig>
<sessionCookieName>kcsession</sessionCookieName>
<domain>kcdev.tremolosecurity.com</domain>
<logoutURI>/logout</logoutURI>
<keyAlias>session-unison</keyAlias>
<secure>true</secure>
<timeout>900</timeout>
<scope>-1</scope>
</cookieConfig>
</application>
<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=”keycloak” 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>mlb.tremolo.lan</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=”dc=domain,dc=com” />
</azRules>
</url>
<url regex=”false” authChain=”keycloak” 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>mlb.tremolo.lan</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=”dc=domain,dc=com” />
</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>mlb.tremolo.lan</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>

<application name=”ScaleJS” azTimeoutMillis=”30000″ >
<urls>
<url regex=”false” authChain=”keycloak” overrideHost=”true” overrideReferer=”true”>
<host>mlb.tremolo.lan</host>
<filterChain>

</filterChain>
<uri>/api</uri>
<proxyTo>https://172.17.4.99:443${fullURI}</proxyTo>
<results>
<azSuccess>oauth2bearer</azSuccess>
</results>
<azRules>
<rule scope=”dn” constraint=”dc=domain,dc=com” />
</azRules>
</url>
<url regex=”false” authChain=”keycloak” overrideHost=”true” overrideReferer=”true”>
<host>mlb.tremolo.lan</host>
<filterChain>
<filter class=”com.tremolosecurity.proxy.filters.RemovePrefix”>
<param name=”prefix” value=”/scale”/>
<param name=”attributeName” value=”trimmedURI”/>
</filter>
</filterChain>
<uri>/scale</uri>
<proxyTo>https://cdn.rawgit.com/TremoloSecurity/OpenUnison/1.0.7/unison/unison-scalejs-main/src/main/html${trimmedURI}</proxyTo>
<results>
<azSuccess>
</azSuccess>
</results>
<azRules>
<rule scope=”dn” constraint=”dc=domain,dc=com” />
</azRules>
</url>
<url regex=”false” authChain=”keycloak” overrideHost=”true” overrideReferer=”true”>
<host>mlb.tremolo.lan</host>
<filterChain>
<filter class=”com.tremolosecurity.scalejs.ws.ScaleMain”>
<!– The name of the attribute that stores the value to be displayed when referencing the currently logged in user, ie cn or displayName –>
<param name=”displayNameAttribute” value=”displayName”/>

<!– The title to show on the home page –>
<param name=”frontPage.title” value=”Kubernetes Cluster Management”/>

<!– Sub text for the home page –>
<param name=”frontPage.text” value=”Use this portal as the gateway for accessing your Kubernetes cluster”/>

<!– Determines if a user can be edited –>
<param name=”canEditUser” value=”false”/>

<!– The name of the workflow to run when a user submits an update request –>
<param name=”workflowName” value=”ipa-update-sshkey”/>

<!– When the below number of minutes are left in the user’s session, warn the user –>
<param name=”warnMinutesLeft” value=”5″ />

<!– For each attribute, define an attributeNames, displayName, readOnly –>
<param name=”attributeNames” value=”uid”/>
<param name=”uid.displayName” value=”Login ID”/>
<param name=”uid.readOnly” value=”true”/>

<param name=”attributeNames” value=”displayName”/>
<param name=”displayName.displayName” value=”Display Name”/>
<param name=”displayName.readOnly” value=”true”/>

<!– The name of the attribute that identifies the user uniquely –>
<param name=”uidAttributeName” value=”uid”/>

<!– An attribute that specifies which roles a user is a member of. If left blank, then the user’s DN in the virtual directory is compared against memberOf attributes –>
<param name=”roleAttribute” value=””/>

<!– List of attributes to include in the approval screen –>
<param name=”approvalAttributeNames” value=”uid”/>
<param name=”approvalAttributeNames” value=”givenName”/>
<param name=”approvalAttributeNames” value=”sn”/>
<param name=”approvalAttributeNames” value=”mail”/>
<param name=”approvalAttributeNames” value=”displayName”/>

<!– Labels for each of the attributes –>
<param name=”approvals.uid” value=”Login ID”/>
<param name=”approvals.givenName” value=”First Name”/>
<param name=”approvals.sn” value=”Last Name”/>
<param name=”approvals.mail” value=”Email Address”/>
<param name=”approvals.displayName” value=”Display Name”/>

<!– If set to true, the organization tree is shown on the main page, helpful when there are numerous links to organize them by organization –>
<param name=”showPortalOrgs” value=”false”/>

<!– The URL to redirect the user to when they logout –>
<param name=”logoutURL” value=”/logout”/>

<!– Optional class that can make dynamic decisions about editing the user’s profile, must implement com.tremolosecurity.scalejs.sdk.UiDecisions –>
<param name=”uiHelperClassName” value=””/>
</filter>
</filterChain>
<uri>/scale/main</uri>
<results>
<azSuccess>
</azSuccess>
</results>
<azRules>
<rule scope=”dn” constraint=”dc=domain,dc=com” />
</azRules>
</url>

<url regex=”false” authChain=”keycloak” overrideHost=”true” overrideReferer=”true”>
<host>mlb.tremolo.lan</host>
<filterChain>
<filter class=”com.tremolosecurity.proxy.filters.RemovePrefix”>
<param name=”prefix” value=”/scale-token”/>
<param name=”attributeName” value=”trimmedURI”/>
</filter>
</filterChain>
<uri>/scale-token</uri>
<proxyTo>https://cdn.rawgit.com/TremoloSecurity/OpenUnison/1.0.7/unison/unison-scalejs-token/src/main/html${trimmedURI}</proxyTo>
<results>
<azSuccess>
</azSuccess>
</results>
<azRules>
<rule scope=”dn” constraint=”dc=domain,dc=com” />
</azRules>
</url>

<url regex=”false” authChain=”keycloak” overrideHost=”true” overrideReferer=”true”>
<host>mlb.tremolo.lan</host>
<filterChain>
<filter class=”com.tremolosecurity.scalejs.token.ws.ScaleToken”>
<!– The name of the attribute that stores the value to be displayed when referencing the currently logged in user, ie cn or displayName –>
<param name=”displayNameAttribute” value=”displayName”/>

<!– The title to show on the home page –>
<param name=”frontPage.title” value=”Keycloak Authorization Token”/>

<!– Sub text for the home page –>
<param name=”frontPage.text” value=”Use this token for working with Kubernetes web services”/>

<!– The URL to redirect the user to when they logout –>
<param name=”logoutURL” value=”/logout”/>

<!– The URL to access ScaleMain –>
<param name=”homeURL” value=”/scale/index.html”/>

<!– Implementation of the token loader –>
<param name=”tokenClassName” value=”com.tremolosecurity.unison.proxy.auth.openidconnect.scalejs.OAuth2Token”/>

<!– Token specific parameters (see below) –>
<param name=”oauth2AccessTokenAttributeName” value=”kcBearerToken”/>

</filter>
</filterChain>
<uri>/scale-token/token</uri>
<results>
<azSuccess>
</azSuccess>
</results>
<azRules>
<rule scope=”dn” constraint=”dc=domain,dc=com” />
</azRules>
</url>
</urls>
<cookieConfig>
<sessionCookieName>tremolosession</sessionCookieName>
<domain>mlb.tremolo.lan</domain>
<logoutURI>/logout</logoutURI>
<keyAlias>session-unison</keyAlias>
<secure>false</secure>
<timeout>900</timeout>
<scope>-1</scope>
</cookieConfig>
</application>

<application name=”CheckSession” azTimeoutMillis=”30000″ >
<urls>
<url regex=”false” authChain=”anon” overrideHost=”true” overrideReferer=”true”>
<host>mlb.tremolo.lan</host>
<filterChain>
<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>
</filterChain>
<uri>/scale/sessioncheck</uri>

<results>
<azSuccess>
</azSuccess>
</results>
<azRules>
<rule scope=”dn” constraint=”dc=domain,dc=com” />
</azRules>
</url>
</urls>
<cookieConfig>
<sessionCookieName>checksession</sessionCookieName>
<domain>mlb.tremolo.lan</domain>
<logoutURI>/logout</logoutURI>
<keyAlias>session-unison</keyAlias>
<secure>false</secure>
<timeout>900</timeout>
<scope>-1</scope>
</cookieConfig>
</application>

</applications>
<myvdConfig>WEB-INF/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>
<mechanism name=”oidc”>
<uri>/auth/oidc</uri>
<className>com.tremolosecurity.unison.proxy.auth.openidconnect.OpenIDConnectAuthMech</className>
<init />
<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=”keycloak” level=”1″ root=”dc=domain,dc=com”>
<authMech>
<name>oidc</name>
<required>required</required>
<params>
<param name=”bearerTokenName” value=”kcBearerToken” />
<param name=”clientid” value=”kubernetes” />
<param name=”secretid” value=”7d08d209-858d-4663-9c20-5ae7d9520c31″ />
<param name=”responseType” value=”code” />
<param name=”idpURL” value=”https://kcdev.tremolosecurity.com:8443/auth/realms/kubernetes/protocol/openid-connect/auth” />
<param name=”scope” value=”openid email profile name” />
<param name=”linkToDirectory” value=”true” />
<param name=”noMatchOU” value=”keycloak” />
<param name=”lookupFilter” value=”(uid=${preferred_username})” />
<param name=”uidAttr” value=”name” />
<param name=”userLookupClassName” value=”com.tremolosecurity.unison.proxy.auth.openidconnect.loadUser.LoadJWTFromAccessToken” />
<param name=”hd” value=”” />
<param name=”loadTokenURL” value=”https://kcdev.tremolosecurity.com:8443/auth/realms/kubernetes/protocol/openid-connect/token” />
<param name=”jwtTokenAttributeName” value=”id_token” />
</params>
</authMech>
</chain>
<chain name=”formloginFilter” level=”1″ root=”dc=domain,dc=com”>
<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>
<resultGroup name=”oauth2bearer”>
<result>
<type>header</type>
<source>custom</source>
<value>Authorization=com.tremolosecurity.unison.proxy.auth.openidconnect.OAuth2BearerTokenResult</value>
</result>
</resultGroup>
</resultGroups>
<keyStorePath>WEB-INF/unisonKeyStore.jks</keyStorePath>
<keyStorePassword>xxxxx</keyStorePassword>
<provisioning>
<targets>

</targets>
<workflows>

</workflows>
<approvalDB>
<hibernateDialect>org.hibernate.dialect.MySQL5Dialect</hibernateDialect>
<driver>com.mysql.jdbc.Driver</driver>
<url>jdbc:mysql://host:3306/unison?useSSL=true</url>
<user>root</user>
<password>xxx</password>
<maxConns>10</maxConns>
<maxIdleConns>10</maxIdleConns>
<!– <hibernateProperty name=”hibernate.default_schema” value=”public” /> –>
<userIdAttribute>uid</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>host</smtpHost>
<smtpPort>8025</smtpPort>
<smtpUser></smtpUser>
<smtpPassword></smtpPassword>
<smtpSubject>Awaiting Approvals</smtpSubject>
<smtpFrom>donotreply@mydomain.com</smtpFrom>
<smtpTLS>false</smtpTLS>
<encryptionKey>session-unison</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-unison”>

</queueConfig>
<portal>
<urls label=”OAuth2 Token” url=”/scale-token/index.html” name=”OAuth2Token” org=”687da09f-8ec1-48ac-b035-f2f182b9bd1e” icon=”iVBORw0KGgoAAAANSUhEUgAAAPAAAADwCAYAAAA+VemSAAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH3wgNASEgW5XIWgAAAB1pVFh0Q29tbWVudAAAAAAAQ3JlYXRlZCB3aXRoIEdJTVBkLmUHAAAgAElEQVR42u2dd5gbxfnHPzO7Ktere6/YxtiYHgIhIYUkhBBIQn4hQCBAKDYQioFQDAaMC800k0DoEHoSSEggoRdjXHAB22BsbNx93XenU9vd+f2xkk7SSTqdbcA+5vM899xqtVU7333feWfmHdBoNBqNRqPRaDQajUaj0Wg0Go1Go9FoNBqNRqPRaDQajUaj0Wg0Go1Go9FoNBqNRqPRaDQajUaj0Wg0Go1Go9FoNBqNRqPRaDQajUaj0Wg0Go1Go9FoNBqNRqPRaDQajUaj0Wg0Go1Go9FoNBqNRqPRaDQajUaj0Wg0Go1Go9FoNBqNRqPRaDQajUaj0Wg0Go1Go9FoNBqNRqPRaDQajUaj0Wg0Go1Go9FoNBqNRqPRaDQajUaj0Wg0Go1Go+m29K7K/X15qf6NNJqvlD+e9sUd+/xf6d9Xs+cgdvcLHNwX7p4MR1/Y8bvpk+hZWkgf06AngiohKAfKUBRKA59l4YndoxISSzmElSIoBC2OQ5OjqLcsagJBNl95D1t0cdBoAe8CzvkFzHkYRFH7OlNg3D6ZMV4Po4VgpJQMVtBTKHoAFUpQjqIEKFSAjN2ZUrH/gHLc/0BYKVqAJqVoBGoVbHMcPrcdPolYrLjkNpYlX9N1Z8GUP+sCo9ECzspdl8KkWe2fp/yesv7VHGqaHCIFY4CBCPopRW8pMOLCJEmk8YX4slLtx+uwbdp+joOjYCuwUSk22DaLoxbvXnQbbwFOspt9x1O68Gi0gJn6e9eyifYrEX+6giP8JkcLyX4IBgsYAHiyCjFNtB2Em2E5n/0cRVAp1gNrLIu3AiFe+ONdrNRWWfO1F/BNF8Dk29s/zzifyp7lnGga/EgIRgEDBHiyirIzEedrhfMUv1K0OLBW2SyORHniotm8HN9m2kS48m5dmDRfAwHPmASX39X++bYL6VtZykTD4Mcxa1u+Q5Z1F7rSnVhlhWKTo1geivDAJbN5Ov7d9efA1ffoQqXphgKe+Es4+Wg45NSYkM+jsncVl3hMThDQFyjYWXF1ScQ7Y5Xj3yvqbYdlkSi3Xzyb5wE+eRbuex5uflQXLk03EfC9V8Dvb0x8lA9ew3k+D+ch6CfAnyKKXSzabPvu9HmSo9uKRsfmzUCYqZffyRKAmefDZXfoAqbpRi70vVdwWFEBtxkGYwF/inXcQcurFDhOwiK6oeJYc1HiOAqUiDUIx29cuHcvYstdEX+W63UcRW3E4r6aJmbNeIAWpWDKmXD9X3RB0+yBAr7vKjjzBrjoRHwT9uImj5czhcIHiB0NNjkxocbbdItLfJRXl1BaXkRxqZ+iIi8FhR68fhPTNJBSoHCwbUU0YhEORmkLRGhtCbG9MUhDbSsN9QHsKCA7EXQeVlmBsm3eD4W5+NI7mQswcxJcdpcubJo9RMBjh8FTN8Lev4J7r2T/kkL+akiGK5D5BqZSxJEk2rKqEvoM6EmvflVU96rAV2AicEA4CBRgxz4nKYpkUwxO7IAq9tmyHOprW9iyYTsb1jVQty2AUiBk1wNgSZ5BayTKNQ2NzLnhIUL3T4EX34G/vaYLnWY3FvCtF8JFt7nLD1/LHwp8zETg3aEmn5hwq3pWMnDEQAYMHUBxiR+EQgjHVTS26zPH/1T8s8ogYNqFS6r6FMoVrVC0NodZ+2kdq1bU0NQQTHG987TCiRURi6cDIS68ag6bAa78HUx7QBc8zW4m4L9cBWfc4C5f+3v8IwfyiMfkl/larnQRe/1+Bg0fzIh9RlFWWeI63Y4DWEli3bUCTr0QBUKwdVMTK5dtY93qBhxHdV3EgG2xMhjhN8+9yZL5y9yvpk+EP+q2Y83uIOCHroFTp7rLd19Gv+oyXjAM9tuRpprisjJGjB3N0NEj8RT4wIm2CzVdsF+wgBWuRRZC0NocZtmiTXy6sg4rauOoHPeT4b4cRXM4wgltbbx6zX1Y0LFNXKP50gWcLN6/XMW4smKeF4LBeVmq+IIQFJeWMWLcPgwdPQrD5wE7mibQr0bA8W3iwa3W1giL521kzSf1WJadvVkqg4iVgnCEkyJRnrtiDiFd/DQ7i7EzOz98bbt4H76Ww0qKeF5KBiS/FZL6OLtvC5H6/igoKmb42H046Ls/oOegQUg3jhtT/s787WIUKKXweA0GDaug36AyWreHaW2NtL+kROqrMdPvYBgcj2Dzt/dn+asLsKZPglfn64Ko+ZIF/NC1cOq17vIjUzmyuIAnpaBPrsIbL+MK8Hi89B4wiAOP/D6D9tkXQ7qVxRTruTsJOMlmK6UoLvEyZEQlhYUeGuuDRKN2LAiW260RAgzJ0UDd4RNYeuUcrBnnwStaxJovS8APToHTYpb3sev4TmEBjwhJ33z3Ly6vYK8JBzPhiO/jLy4BK7KLRPvFCzjZJZYCevYtoU//EtpaI7S2hHHsJBFneZHFRPxDIag9dByLr7oHe9q58NoCXSA1X7CA77sSTo9Fmx+7nm8V+HlACgZ1qFRnsERSSnr0G8x+3/oB/ceMBysaq7OqPU7ACbfaURSVeOk/qAwpBE2NIaIRJ28RmyYbSv0suesZ1NTfwxuLdKHUfEECvu0imBgbcP/4dRxQ6OdeKdgrvX6bLmKlwOPzMmivcRxw5DEUVVZDJPwFCfdLFHAMxwHTlPTpX0phsYemxiChoOV2NxMZRJzqTv+kT09W/ncey99YBJNOgPnLdcHU5IfMd8OBvWHiL9zlR69jhN/PrVIyNt6nuEPpTHI1/YVF7DXhMPY78lhMn9+1vN0MRymUUowcU8Wh3x5Irz5FdOgBkuE3EgLh9XDfrAv4LsBBY7t23mvO7Hyb83Sivm5L3s1I/7oVfnIRPDiFnqXF3GlITkgXajzIEy+0jgOFpWWM3v87DB53MFihmMscbxKK/8WaflT68u7TjJR5XfKn9v08XsmWTa0snLuJzeub3c0EObuQ2g5rWtv46ZVzWDHzPLjsztzPI3ns8bfHY3z/UAYbBj2lpAi3P3ZLKMqmqfeyKb7PWcfDn/+mC/3XTsAPXwu/vRZuuwDfgL5MMQ0mC4EnVxuvY0NRaTljv/FD+o+eAJFQkiC7t4AVbnNT7dYAC97ZyMbPm3FiEepco52iNq80NnHCdffTeP3ZcPWfOj6LC34Nh4yFX18J556Af1AvDhGSIwQcCAwTggoFCocaB1Y6inejFq9OuYcVoLtyfu3qwDddAOfOdJfPOI5fe0yuFIKS5IBMeh1YOVBUVsa4bx5Dv70mgPVF13e/2jpwRpfaVpSUeqmo8rO9KUxLc6RjcE+kB/kY4vVQ8PI8Xnp9IZzzc1i4MtVdnvkwPPca3Hguw6rK+IMhudow+JmUjJSCaiEoFoJiIeklBWOF5EeGZMyRBxB8dQHL314Ml5wMc5fpwv+1qAOfFav3PjGN/U2Ti6WkOmcbrwMFhcWMO/QY+o7Yt1vWd/MlGrGp7lnIwYf3d+vEacG9DL+f8Jj87uY/cBLAgUn14RvOgan3ucvTJ3GIz8sdpsEVUjJYZAsixh6wIfmOaXLn9ImcDXD2cbrgfy0s8KPXwUGnwCPXU13o5QrT4EeZjIdIqtOZHpNxhx9H/5H7gWN9BZZ397DACUvsuJa4rMLPtk0BQkGrg+UVqUEtnxCMPGw8r11wM/U3nA1HHQJXzHG/n3U+x3lNbjYMDhedVYBSj1skJAcePoHlx01m9dVnwFsfaAF0WwFfegpcMttdPulH/MpjcoWIby8yFxSlYOyhP2Xw6IN3QVfI7iHghIjLvBSXetm0vhnLUrl7qwmqTZOKl9/j768tdHtpXfpr/Md9l4keD9dLwchsHlC2przYtsWGYFhpKY/96VlsXfy7sQv98yPd/09PZ2/T4AIp8GYrGOB6ysP3PYJBo79BF1qnvjZYlsOAwaXsf0gfhBAZf8Ok1dKQ/PjmC/gtwLVn0aN3b2Z4TK4Tgv6ddVXNJWIpGTNhJMfrJ9KNBXzP5XDwqfDQNRSbBicakvFkqWMJ3C7MfQaPYPi4b2N6fbudBdxdUI5ixJgqRo6pTPmJMlVHpKDS4+H0GydydHkxc0yDc4WgNNsLtIOIsywrQZEhORbgqG/oZ9ItBXzODPd/SSHjPSYTMwk3vsLtqFHIqAOOpqC4Sms3l4AVSCk44Bt9qKz2k6s6ErOWBxUXcq8h+ZkQePJp9Ms4Mir15WsYhuuCv/yefibdTsD3XuH+f2o6VdLgNCkpy1IQALBtGHXg0VT0GhwvQvpXzSkwhb/A5NDv9E9MwJbDlfZJQV8EZkaNp+2nFHg8AiFETldaCPzs5FBSzW4q4Hj+ZkOwj8fgdxldstgKx4J+w8bSe/B4hOHR4s0Tx1H06F3I2P16un1RMok4w3IuUVpRKK/wcPTx/SksNnO70grj5KPdtnxNNxLwPZe5/5+ZSZXp4XQpk8pJhj7OHp/JkLFHUlBSldovUJOHKYZxB/SitMKb+fucg4pTP0ajMHJMCUf9tB+9+xXg98nsx3E/G316UKwfQjcT8DkzE4VrmCndzgRCZHafbQsG7X0E5b2GaNd5B/GYgoMP6+s2l+dwpdPc6iRT7n7+5nd68M0jelJS6k7g6PHKVEvd0WobflNb4G4l4EmxESvPzaDUa3KKIbO4cLi9rUoqyukzZH88/hJtfXeCfgNKGDi0OGnKiByub5IQHQdKyj384Kf92Xt8Od641VXg9cr2+nVmERsIbYG7lYDvik1YrQTVZnrdN63rn21D/5HfpKx6EO2VOM2OIKRgv0N6Z38HZnClbRsGDyvmh8f2p//AwvZ25ZiCPd5O2uEFRoFPW+CvkjsuhvEjd/44JsC9V8Lvp8FT0/CZkp9Kw50pMMUrjo+kcaC0sooe/ffG8BWCHdFPYycpr/QzdEQZn3263Z0NQrhpsJPncYpntTQMwfgDKxm7byVer0SlKV/hWuDk/RKPL/YMhcLTFmLAKUdTVV1Ogd+L9PswfB6k7SAjUWTUQlo20nGQSiFjGYSkkEgpMaRAGgIpDaRhIAUYjoNhO+4+juPuo5TbHRuQQrjHkCJxHGlIDEAoB2zlnl85GE7snMT/x86fOI7AkNL9j8J0VPt1ohLnlcTOGf8f21dKgSGEa8AS+ybvF7v22DYSMOL3L5KOpRTSUUnXGds/ds2J+44tW8CSSIQ559/COzeei1i6yn3Mb94PD/wdHv5XFw1A8od/3EyVIXnFY7JvthSw0SgMn/B99jrgZxheXyyDZGyIYIchfUlDAlOGB3b/4YTJ3yf2TP5R43vF1jXUBnn+ydVII3uy++JSDwd8oydDh5fgOCrDfYFhwPtv1/HhkqaEuHdkaphO90u7vq7um/e2nZxzT8WymWdZ3LitiZf6VWNPf9Cdl2/GJHh9Uf5t9Alfa+rZiEiUEabBvtmCH0pBQXERVX1HYxSU6rrvLqSs0s/IsRXtXcjT6DugiO/9uD/DR5UliTdLcMwnMwYf83HRc+4ndtQ0dO0advg4exCmwSF+Hy/078H7ls1PJ51A4cW/QVx+lyvei07KU8BzYk1H44bh9Xn5PyGz/2COA9X9xlBePZj20KlmV2AYgoMO68OQEWV4vBLTlJgeSUGByZjxlRz5w/5U9ijAinYec/B4Ze425Rx9pTsELbN0n824X1cGVuzM9Ylu9NwlE/xe/l5axEuGwZHn/9qtvt76mPv9Ob/I8z35t1mU+7wsNQ0GZnR5YuVmzDd+w6B9jwYrmOa25utCZ3B7k7/vkHLn6+FCuwXTncalZksbNVsDSEPQq08hVT382LbTXt9VyRY4dZ2UsGplM+++XoNjqx2eGP1LcaV34cTt3QGlwHZ4xLK5NRJh+e1PulPwXHwS3PJYlhcAwJzLEF4v44r8XJLt7ahsKKseSP9R34l13OhiOtj4qHMBthXGCoeJRkJEI0GsSBg7GsGxLbcQG7I9nWU3GE7Y1YdYVOKhV99CevQuoMBvJgmxc9MjBLS0WKxb3Yqi6wMeMm7bBcsK+VnlvCw4+Vv/7oAQICXjpeR4aRA4aG/WzvuQtveWwZQzYGs91G/PEIXu3wsDOKaDbU57I5ZWD6K0aqCb8KorVyUkdqiNYNt2QoEmttduprmxllCgmWg0Akphej34C4soraykvKqKwpIi/IUFSFO4BvVrhOOo2D2rHXgBKMorPEgZm8xR5DiM6NjSILJZNtGFyxGZWzAybtLF6+vsuN0BKejpMbnLkBx66SnMmvUIS6/7C9x9Gby7FP76UpqAwxFkoZ8fZWt2QIHpMSitGoRRWA6R1jyvxMCOBGhu2ETNhlVs+WwFDbW1KCem65hFjhtaR7nvBtOEqj496T9sKD369KKkohQhpQ6a5WnBKyq99B9YyNrVgfbc3Env04xNS6T0JUGReV3W/URKraWjS54ruryjrnSml81u7FpLkX8QTrj14xOFyZjLfssVC1fy8sSZOBtfdr97PCZi87ozIRSiorSQ/bM9YOVASXlvSqoG5hm8EiANWuo3svmzJaxbPo9ASxDDcMWZrd4kY68UpaBucw1bN9RQ1bucYWNG0W9If7x+b6zkaDqz4AcfVo0VVdRsCxFsc9x25BwiyWh501Wb/pVorxkpBELmacFF8onTriPTcswzyLSe+LXncuszfqeyftfhdkWOQHgnEfT415YFLc1uM6yUeQfilJTsa8IDB4zhoiI/T/U/CuepGbBqAyxYDuaooQhDcqCUOd62DhSW9qKkMh/32TWtNZ9/yKeLX6F2wwakBzzeNNHmcodc/eM1oLG2iQ/emkdjzTBGjB9FSXmx7nadhxUuLvFwxPd78eknLTTUhbFtBemBLwECgULFCpqrFJFWIRUiHmQTafummmvRoX6rUuu2sbiGiI1pFOnHil1b4jyJ/VT7NDUq/rld1e11a5U0pU1a2qLYMdqvUbn3lWEblUGXmSx9ciqpDtup9hecUhAOwaaNUFsDTU0Qjbht9nkYYqSktymYM3oIvPAWT/zqcph6VkzAfh8IODyrv+R6whSW9sZTVBWLPueyvIJNH7/PivdfIBgIYHqTbixLD6OMblls2TTcbVZ/tIZgIMDYg8dTVlWqRZyHFfb5JeP3r0AIhW2r9oKNShVQ4gmlRuBVSulUiTp27AxJAfC0qD4qqVC376fSz5Mh+p8SN09E+VVaq0LSVio1uq/SfHjVwadWsV1EautC2mnjh1Wx3ocqaT2qfdmJt9Co1G2SL8tRUFQEldWwvQk2bYCtW6CxwR0UJPMYmS0F5abB7EtPoXbWI7xyzZ/hot+A2asSUb+dgzsYwaTue76CIorK+4KQuYVjetj8yVyWz/sHkZDrMsdvQuQr4kz1LAGmFzat3YptOxxwxP4UlhRoleZhiaNRh47NXirLuvbIv+rga6ssYs1PwGQQsErbJ7eA0w6XxTx2eHFkMJ+ZQinZBJwuVpwMAle5BZy8rqQURu0Nvfu6Qt68CbY3utLqzK2Wkp4ek9kX/YaflMO6KY+7/VflPsO5TQj8mZoDHAVFZb3oO+JwCkqqYy50hmYb00Pjpo9Z/u4zRNpacg9ny9WMkSvFjAHNDQHC4RB9B/WJ3XD3akbS7MYvRHa+aMUtut/vWuTSMje41dYKUcutH+f0qQU9haT8jY/45/otKHnYePpIQZnIEXjw+kspKOmZfeSREDihAKsWvUiwtbFDfShT+196kEHkKXLTCxtWb2TtJ2u7V5cczdesiuNqq7oHjNkHRo2F0lJ3pFlnmJJTDx7Ld+6/BmR5KaPj3SczzWcL4PWX4C/pkV3AHh+fr3ybppp1qdkWM4hYtN9Aq2XzhmXzpG3zjGWzQCk3V3FnHQeEhJUffEww0KZLgmaPF7LHC4OGwNjxUFkVE3duK4zX4NrTp4Lp9cSShKdVQOP1VMMAX2EFmP7M7b9CYLU2sHnNAhwr6laTczS2Ow7Nls2dDvzPsdlq2bRKiZCCMiEYLiQnmZLjY0O1Mh5HCAgFwny2Yi17HzhKe8yaPT5WIYAePUGasGIZNNbnDm4ZBoddfBIHmRKGZAxAC7fCbnh8roCzdaIwPGz7fCmh1gaS5rPOGNB2HNaEopzZ2sr806cRSDvSRmD5Q9fwbqGf5R6TCxGUZutkICWs+/hzRk0YjmFoV3q3JGs7bY4mj06OlXFTlelzvIEsRxBLZAliOW7ASiQFqMAd5IvIL4jlJMf1nLTQTGwbkRy9jn1VWQkj9oKVH0Fra+5aosfkFBPBgMTPmKFXjunx4ysszy5gaVCz/kOi4TakyB5ptm0a20L8+tdXsgBg84tww0Mw5xl3m/uuhDOnwalTqZv8W6YdNJoS0+BcwJ/x+gSEQmFqN9fRe2DPbmeFhXA7RsRvOqWFMn0QBSBUPLYcayCJvdYzD75ID9smHT9xviQ1JDUlpEScY+vj9bnEWAsnFlGOlWZHuetUciQ79r2Klfr4cuI7VKKlSsXacZK/U4lLV+3nTjpnQhROe9OSUqnLich4evNQpnUq+Xip36W8AJK2z7g+/YWS1splSDDCJLSUyzRJyTGmlPRJ8VLTXoiG6cfjKyVjh2QhibbU0dZS55YXkdV1tiNRZsXF+5eroe/RqYc6cxqc8mM46+fwzdOJ3n0p0/tUc4CQfIscrvTWDTX0HtS9BOw4ikBTgLaWEAlbItoLZ7ygOmmf438phT0uEtJEklVI7YXcSRYIqUJLeYlktHAqo5VU2VfkiPKq7F0kVRZDrDpaeEUOK55BTOTYvtNj5XGMDG4x29tg9UZoDZCS1yxLXbifKQXVuTqbS9ODx1eU2QJLSWvTZuxosD2ynKGN13Zo/mgNtwM8MAV+d13mC3rk31Db5C5PnEXd09P5p0cwHkFZNle6sa6J7jYuRUpBYWkBwpA0bWuhcVsLwdZw7J0p2l+UGQucyl2wc5S+fLqa78ru6OJL2XE3ebN3cs2GhOaA20WyqSXPO3P4zFSK8mwdKFwP2YPhLcj85IQk1NqAbUXJZnwFKMfhrWvvI9jBSmfgP3PhT3+Es6dDMMyrHpPTBJRl60gfaG7rfuPKAMOUFJX6KSjy0mtQBaFAhMaaFhq3ttDWGnHvX375LWm65e4LeGELaGqFVetheyD/4hy1uctUuOlFM3ZrBKQ0MUxf1tdKNNKGig1wyDZKRSmWAUw7N78LO3u6+/+dxXz848Noynp9CqxItPO4+x5bD3aFbBgS02tQVOaj3/BqQoEITTWtNGxtJtAcTlRfdjtxqa/AHn4ZBjf2WyeaOEXayy3TWOoM/SDi4m3cDivXQWtb/s8wavGEUvzZNCQFWeqtsRNIDJll2hQBKtEzK2v9F49JK8BV93Ttd7rveYI/+ibh1M7wHd0S5aivhWUQUiJQFJZ4KSiuoPfQCiJBi6aaFhq2ttDaFMrPxVU7/kJJFN5YCRYkLYv0ZbeEirR92wXQrgSZ/H369smfE8sq9XtJ0gALldg+oRehUjsQJQ2QaF9WKUVMJZextCpJeiQ6366UJG0jBNTWworPuibeSJR7leLCmx8lbBqyfeKsjKPHYmkCshUEaXgQQqb0pEqvp0Yteu5IgTlsAl4p8Wa7vkTEW4qv31jh2ARmvkKTXoMr6DWonEjYprWpLTHeOlkcIklgSiYlSJGkFOz4dirJwogkixMflCBy9IVOH1DQ3jdaJUYNJSK4oit9odPTHaWGeTuel6yDGdKtQVf6Qne1BSz93RnXVm0NfLAQgl0T7y2Ow9U3PUoQSJ31LpOr2j7qPmO8FK+/BGmYHQ1wkistJYd05SYfuw5OmgJnH8c4oCrXgHSvz+u+YGydYN7jM6joVZxW+JMDW9makfIfzJBPDKljuU4etKh2QRQr88mSWn53a5SCmm2wcD5EwvmLN2xxTdRm1m2PEbr0FJj1CEjbwcoaMUu8LZ3MP7bjUFjao72OnHkggvCYHPCXqxkO8PSM3Bf58LWueAF8Xo4BBuYa4FBaoad20eykoBw3jOI4X3xRUgq2bYP57+UvXqUgEuWSSIgZtz1G6JKYeMEVcJDs+gBl42TLwqEcSir64fEWtntHmTMjeKrKmAFwwuWw4mm47uyOh3v0OvjttQkhj/OYHCsFhenRz6Qx2VT1qtRdKffIKoArHMsCK6ry6sT/RYippQWWLoG33oD578PWrV9cTNRxYMtmeP9d977zFK8dsZjY0MIds58kMvlkuPmR9u9NAa3QcZ4ckQgQ2ThWJLO/oxT4iiitHkhLwwYcx87WSU76PfzsmZlcbEWZPeYEEo9rxkS47C73fCfHLO9DU+hdWsSNUjI+3eNKcaWBPgN7oRW85xEKKtZ9FmHj+ijRKFRWS0bu5aW0XHwpAUmlYN1aePUVh2ikvWq2bAmM2RsOPBg8nl1XsmzbzcixYF6qvjq5xmDUYtLazTz8zP+wLz4Jbno0dRtTCJqgvTdWuogd28K2gtnPaEfpNXgCNeuXEgpsb4/+dew9ZRT5uTkk6fHUdG6NRmk+aQqhy++Gy+92N3vqRgodGOX3MMMw+H6u+o5SUFpZQnmPiliKW80eEnujqdFh7lsBNqy3EsGx9Z/Dyo8iHPE9PwMGejodF7sz53ccWLtG8cr/3H6RyaltlIIVy13L/M3D3UwaOy1eC9avg0Xz88u+EbuOxqjFeTMf5nHInhvaVIq6XPEBx4lgRdqyRxxsi8oBe1NS2ZdwcHuHlDjpmTj8Xi4zDX7hMZjz9HTe9ftosmykZdHXMPiJafJbKahIBA9zZPAYuc9IXf/No8ASzzHlkMiDlaiDqHh4SbQPi4kFg1Q8eKkyR2t3TLw2c98OsHGDhcdsf65SgmUpXns5yBHfhUFDPBjyixHvZ6vh9dechBeXyfNc/zkEg3DoYVBVxQ5FnIVwc1+tWQNLFuWVAyv+G2+J2lww82GeATd1TrbE7qaj2JyrnuJYESKhltw2XwkGj/0uLQ0bCbe1tBvLLClLTYNhpsEtsW6WtsfE8Jqp26lk4abnK3agslcF/U+f1jsAABu8SURBVIb252uXNLqLBSgcdgi0RomEHRxbIQ06tJEmEsK1xyySmnfaWxL9BRLT3DERCwGNjRbvv9vG5vVWIjtp8jOWuP2v33glyGHfVgwb7tnh82UT7+pP4c3X7YxpnJKR0o0Uv/EaHHQw9OvvWk+nS789rPoYPlyalI21c/GujUa5cOYjPA9w4W/g1sezb2+i2Jjb/IeIhrbHGgzJ6kZXDhxHz4Hj2PTJXLeTPZ3nEhYCTImRLb9RpmM4Cjx+k30OGZc2L64mvQAFWi0+/nA7az5toWW7nWjbjLf/xoUpZXsfaynduTeFSF4HPp9Br75eBg3xU1FldklUQkBjg82C99pYvy6aKMwqy7ZKwduvh7AtGLGXB9Oz85bfcWD1Koe333JSg1Q5LKthQFMjvPs27LsfDBvu1osdO7cxlhLa2mDlCndYYL7idRxWWjYXz3yE/wCc/39w2+O59zEdxdpcN25Fg4TbGjqvdVtRRux/DG3NNdRv+jS1KWoHsutnGj6oHLeA7bXvaCp6VndthoivGbatWPReHWtWBTBN8PqSvGZSlx3HfTOq5E4SyT2HAKUsNnweZtP6MAceWkKvPl53Bok83eaF89r4fG00pTCLLJ5W3AuY+3aIaFQxeqzHDSipHRfvp6sUc9922qO/WaplmUQcCLhttoFWGL23Wy+WGcYEK8c1MK2t8NFS19p3QbyLoxaTZz3CqwDn/QrueLLz/YwTvke5z8vJWQ9sQ2FpT3oMOqiT+ZAcpLeA0sq+tDZsJNiyPbW/KPkltcuWTkc5IA3B0DEj2GvfMXTXuZF2lfXdurGNxfMbMMyUrrfZc5Fl6rYo29dL6RbG5mabupoopWUmZeW5LbEQsL3JZtH7AdauiWCaqe472a4p6do2bbCREqqqjS6LOC7MVR87zJtrE42kDtHLdwZWt37u9pwKu4PC2L7dTQvb0AAN9VBfB3W17t+nq2DtZ/nXeR2H96IWF816hDcBzv0l3P10fvuajc2sLCnMbWAjoWaibQ14/J0kVbejFFcPZNTBx7N68b/Ztv6T9q55OVzpnHPyxELwPr+HQaOGs9eEvWOjnbX1zVpwpWDLxmAikpspFpEr7pHr2XhMaGyweO+t7Rz4jVIGDfNjWyqjeJqbbBbNa3PFm9SdvrMpXdLFs3hhhGgUxu/noaAgP8crLt5PVjrMn2e7nSZkjpN18uPEZ/b5dJXb/BRLUoFtuxY+uR3bMLok3tciES6/6TF3rPxZP29PcpEPRlUZgbFDOV9KN/NFRpV7CyjrORJfSY/saWXjf46Fr7iCih4DAJtgcx3RsEWueYfTP8dfJo7jnq68upwR4/Zm2NjRSEO0Z9LWFjirgNevbaWhLpxVpDszK6AUEAoparZG8BVIqnp4UiyjEK6lXjivtd3ydnaetKBa+uyI27a6FrS6WuLzJ6WsySZeR/DxSoeF822CwVg9P+neRSe/Q7bjJgs3fs9StovWMMi7Ccx2+HfU4pKbHmXx5n9BUwDu+3vXnrVx35WIQJAfGAZDcl14adUQiquHgRPtXDyOhekvorLPUAqKy5FSEQo0EY04iUTtHVLL0j4NRVy4RSWFDBgxlBHj9qH3kFiPyrzEqwW8eUNbQsBdnTQ7n+lEpYBwRFGzLYJpSHr09iT2bW12WPBegHVJ4u1KdUkkCybp+7pah1BIUVUtKSgUHXpMqVhwTin4eKXNogUWwWCqNcz04nCbS1mhFJulpHc+1j35b8diFDwbjjL55kdZef8UuPcf8PC/un4c49gjABhlGhyW7WLtaIii8n5U9B+fn4Bj/aeFlJT06E95j76UVvTGX1yEEAorGiIScnBi7odKckH8BT4qe/ZiwIhhDB49hoEjR1BYXtq+YTefH3iXudAb2qivDeVOsC92LMF+cv3ZikJtTRQU9OrjpS3g8P7cFj7/LIJhknXysKRmk1ql2CwElTmrorFvGuod2gJQWSUpKY1FyQ33zzTdF//HK20+WGTRFshiDUUHS7g4EuVsBf8TgiFCMPiLfD6WzSORKJff8hhrb7oA/j0X/vbajh3LDIbBNHjb7+XyXIGstuatOMEmpOkD8qx/KgVWFH9xBb2HV1DdfzhtLXWEA9sJB1uJhALYVgQhFIbpwev34/P78RcVUlRagjAN94Vh2+jukl1AtRfcfKYFTY4Cd6WeGn8pRMIOHy4JEGxzaGtz+HxtJGH1Ms0BHNePrQjaDtdHLeZ6Ta43DH6U6/ri9dq1n1lEoorBQ4xEOYtb36ZGxepVNm0BN01NZ/Nw2Q6LIzZnznqERQCTT+E8r8kVUvJ/X5B4724Lcf3tT7DtmjPdNt4tdTvxsp52LgzrT4/qMmqy+e6OA2U9BjPykNMo7TkS7BBuk3bsT9kxUSd/TvoeOzaiSbX/KRtlhVFO1I1gG/FR3Q4oy53GVDlJx7XTPjsdP6ecO8scPEqlji9NNKdkG3OaOpFt5uF56fulpyRMHpeaPtdQpnWdXZ/KkByu/dqkFCyZX8fyJY0p79LkPZRDS9TiBdthvoBCaeCVEtO28TgOHgSmEHgMwS+FoEfST5B81pSB7IlJxdPOmWm+YMchaNlcXb+du295jNA1v2ewz8Ns0+DYnHMC0z6/gK9AJKpd8bdCJKKwoqSkoc02raplsyQS5XczH2bx326B4y921198Ev19Hi6Ukot2ZVeDqMWMtjCz7niCxsmnwE2P7PwxjdcWwGnHYAnBUYakX/YAc5DiyoGU9NoL7EgXXNl019qOCdTtyiYMN6t7wtSrmFCV2vFz7OHWWgiB6ZGYHjedTns6V5X3/tu2tFGzJZTdBVY0h8Pcd/Ht/GlQHxZ4Td6TBnNbg7wTCPJ21OItx+FVYJEUHCYE5blca5Epipul+dBRhC2HaxpbuOvmRwm9fBeccT1N3xzPm1LSX0rG5mrXiYvKirrNO9GouxyNZVcSebQP2Q6LozanznyIpUrBFVfATw6HucvgvWU07zea901JnRAcLgSenXKIFNg2V7S0cfNdT9G8q8QL8QH9Csex+Q8mB2X7waKhMK0N61DhVoSU2adZ6Yqfl5x8V7vIid862BZlzceNNNUHkVLQo08RA4aWUlBoYEXz+93jvalyzfwY71h/798J5zjUv2edx0k+L49IyZCsc0gn1amzdlN0GxAijs3U+ibuuOUxQqoNTvkF3H81nH49W64+nUl+H0HT5NQOx07rfJG16SuX2y/AslkatThlxoN8BHDsEfDCW+73fzgRZv8Vbn+C7ef+nLtKS9gkDaYbXagXxxO7CwgqWGTb3BUK8c+7n6btkpN3nXgTAl63Gbt3NS/4fFyTK4jQUr+O1sb1lFQPc62oZpfT1BDivdc3EGiJYltuIoUtG1tZt3o7I8dWMmBICQLSJuzOIOB4bqrsU7lKaeS2LNPOhSvnwKV38s6MSfzc7+NpQzK8s3b8lPMkjZFwFBHL4Yb6Rm675XFCSrndBR/9Nyz9NJGNtG7qmfzBUbR5PZybz3myzS2daT/bZqll8ZvpD7Ic4Jffg2deab/n2X+Fc34O9zwHc54jPOnXPFvgZakt+IYUjBQCy3YICGgT0GbZtElJGxAMRwgYBkGfh7ACB4HtOLRuqGHLX/+DPfnkjsMBd/qFH1/4562U+bwsNA03c0ZGgyklex18Mn3HHBWb6LuLdeD4csrnpHqsSl/+etWBrajNmy+to25bW7s1oz0BmumR9OpbxJh9q6js4XdrHHFPKK0OvGJpA0sW1Kd0iUyrxzbYDlMmzuTunCI+B668JyHoEUUF/ENKxnRWv0yvxyqHiGUzY2Md0+58ggjAH34Ns59o36e0CKZPhImzYMoZFHs8XO01uTRbXbjDOXPUm2N13qVRixNvfIAVnQnj10fB2OFwZezXmXgCnkI/PiFQkSiOUigJTt12nKiD87+5qPrm7G5k3LLvaow5l8GL78KJPwSgV7bmJATYlsLjL6S8eiiGx58qlN3ubw8LHCvFtk0trFxa50ZwUzIoxiKmlqKlOcLGdS20BWwqq314fbJD90IhBA21IbZsasvc3uoSQvH2i+/yfq7rem0hXH82vL4QXltAwzf35QWvwZFC0jtXM1RqVheitsMtH3/OdX9+jii4Q+RuSyvQ4Sj8+12YehZMvY/ItybwrqOImAbfyaf9OlszmUiyvHHx/vt2sBxYvibzfX+0Bl5bABN/CQtWwILlOHOXEnl3KdF5H2K9/xHWvI+wl32K89FqVDCtEtK72s00GWfeh19MuTFefDcRP3JGDaHO5+HsbJE3AUTDLZRUD6Gwop8bKdYC3iU4jmLp/K20NkdSOjAkN9fELbJtKRrrgqxf24I0JOWVPqQUSW6roLEuzJaNbZnF5bqeYdvhnf/M5b3Oru31hXDt7+GNRfD6QpoPHsuzPg/fkpL+mQSTdp6obXP7P97hyn++4eZfu+Tk7ONbwT3P1LPg2nuxvjWBeY6iyTD4QWciztZzzLZZZtmcNO0B121+6S53FpAnX+78uSxYsWPPs/VLmvk20XD01Csov5f1lp39gQoJwebtNG1diRNu1Wn6dyHbG0Js3djasdeQ6CjiuLvY1hpl4Ttbef3F9dRtC6ZaWpljEINb6KXtYOR7fdfeC1POcJen3kd9XRM/sCxey9U5RLmW90+T7+DSuR+4nQcmnwI351EPvObP7ktj6n2EbZs7wxFOdxTRbPeTySIL121+I2LxqxvudwNWajU8+i944qXuUW4MgHsuhxffgWMOxzYNlMfkmFxR0mi4mdLqofhLe3QyQklb4Hyt7+L3t9DSFM46WicXbQGLtauaaWmOUt2jAI9X0tQQZtOGQMZE+DEits3cl97j7Xyv880P4IrT4O0l8PZiIsMH8FRZMeMMg70yRGKjts39k29nUnzdpV1sPnljUeK89v6jWRKJ8CchWBaxmBe1+G/U5n/xP8vmf1ErsfyKbfOvUJjr28LcMuthtsaPueZTePyl7vPi71A0XriFQQU+PjAMKrPtZEVh2P4/YfA+P0KanpgrrYNYOxrEaqgN8OoLa1IGfKiOp05ZzhTIUQoMj2DsflV4TcGCubUpswYkX6ZStEai3HT+zVy3IwXnnF/APc/Cqcdg7j2Um7wm58dGnQmlaIlGmT35DrdV47LfwsyHd7yQnvvLro3QSSdbPqnuQKLIHDo+8XzrLZt7c5ptA7ateY+WhvW5M3VoOg9eOYplC7ckXN6sAaG0FRnrgAIcS7FkXh2L5tVlzqGeuu8Ouyr3PAtn/Awe+ifW5Nu5cHMtfUJhjg9HOGZjDcN2lXhh58QL3Ve8WZ2zf97KPgU+FiZPa9LBCkdg6IQfMHjcjzE8vli7sLbAXbXAG9c2MffV9YlOFbmaYtKbRjI1oeSzn+u20xi1uOa8m7hzZwrQFafBjQ92XD/lTLjuPv2C/tIsMMDsixKFYUOnVtgDmz55k5a6dflV1DQdiEZtli7YnBq4yjzELgQ0x98MIktkOWvwK7PVDlg2q3f2HjKJF7R4vxIB/+FW9/9PL6bJsnnEcWjLFcyKhMJs/ORNwoFGHZHeAZZ/sJVgwMo9rM/NzP9gS4DfOoqFkNTtUXRRxKnLdfXNbhYITTcRMLgR6VigapVl8+ecVtiErZ8tpn7Th7E5grWI86q3SEHNllY+W1mf+b2XWkldGrX403k38Y81G/luOMo0R7EKsLrs+LRvG7Uc5t3wF+rmPaSfx55Mh3bAF99x/z/5P8L/9wMapORYKSjKVSZa6tdT3ms4vsLyeA0L3YyUzXMRhIJR5r2+jmjY7qwTRNCyeeCsG3l60aNw0hQi/3qbt76zP//1mBQLQU8hKO1qNg0Un7a2Mfl/71Ozaj18vkULodtYYIA7YuMiWwIst+3cfWWFhGBrM+uWvUQ42KSNcE7xgm07LJu/ibaWSKf1VOD92kbmADz3Olx+Knz8DPzhVladcQNnhCKcbju8oBSNyc3HuUSsFA1RizmTb+ejZ2bCWx/o59KtLDDAf96DhY/AUecR/dX3qZGSb0iZef4kcAdytzTUYnoMynoMRiTS/2kLnG5916yoZfWK2k7rqUpRG4ly/QW3MH/O5XD5nfDOErjrGZgxCV6ZDy++w5rt23l2zHDqpaQERR+EO8IsU/9nx+Fz2+auc2dxC7j5E5Z/pkXQ7QQM0NAMK9bCEy9Td+JRhA3Jj4XIvr00YPu2z/AXV1BS2U8LOP2HNiSb1jXx4YJNuROiu2qzLYcnT7+e6cnVmjivzHf/z74Y/vx3nH+9zaLDJvCq10MrseQYgEcphII2BZ8rxVuRKDdPnOW2Ltw4Mb/E4Zo9VMAr1sKzM+HpV+D4I9lkGvSSkgm5PGQFbK9ZQ2FpNUUVfbqYhK77Ctg0Jds2t7Bk3gbCISt3SlXAVixpbmHSf96j6faL4aUsvdNfeg9GD4bZl8A5N9L8z7d58/B9ed0wWIbiAwfedWz+bdk89uFqbpt6H8vAHShw9T268HeLallnG2x/A8q+DS/cyv4FPu43JONzbe84UFBcxqhDfk7lgDGx9Dtf344cpkdSt7WVxXPXs70h2On4WaVobAtzxu+n8benboRfXZHfg5z4S7i7kx5LN/8BLpmtC/3XwgLH6dcD/jMXnniZLSceRUhKjhCCglyBmmgoTEvDegpLqiio6PUVDTvcDSyvx6C+JsCH8zfSWN+WT0J1J2px2xk3uIFDpWDl2vzOlc+wt//O0wW+u9FpR+bzboLnZrrLm2p5yrK4X6nceWWlAYHt9Xy64AXq1y0D0/u1+2FNj0H9tlY+WrCRhppASoKmbCK2HP65ocat995+MTz3mi6gmp20wODWg+c9CD+9GOtnR/CR18NwIRmdy/8WAiLBNlrqN2KaXop7DPiSLfFXK97aTdv5aNEm6mtaEzmNs1ZcBNgOH7YGOe2S2dT86Y8waZYunJpdJGCANZtg7SbY+3Bajz+Sjzwm+0vhZmTI6U6HgzTXbwQrQlmfIcRnMuyOAhYCDFOy5fNGPvpgI011bSlTyWSzvEpRGwxx4tnT+fDvN8PJU3TB1OxiAa/bDIMVPP8mPPVfan/5XVYaBt+UgurOCrUVjdDSsIlQawMVvQYgPb7OJ0nbwwQcn2x83ad1rPhgEy1NocT95xomqBSRcIQTz7iB1wF6lsP85bpganaxgMEV75M3wrOvwhP/ZcMvvsenHoNvC0FpZyJ2bJvWpm201G2msLgcX3k12Fa3ELBhSCLhKJ8s3cLqj7YSbIt2mPgqU3ORUhAKc/LvrudvALPOh2kP6EKp2YVBrHT+7wp4LlY/O/YiXg6Gmegoajo3UaAch4ata1kx9wU2LH2LxMxUeyju5Usa61pZ9PZnfLZyK5Gw1Wl9Ny7ecJTTNm7jGYA7J8Old+gCqeliGdzRHf92Exw/2V1+4RaOLizgYSmoyttqmR7Kew5g6PhDKe7VDyKh1PzSu3k7sJQC27JZt6qW1Su2EApE3MtJEiik7JpYVg6EI5zWEuKvk2YSmXMZnDtTF0bNlyhggOdvhmMvcZf/cTPfKS7kCSno1ZXT+wqK6D1kFANH749ZWAhWmI4dP3YfAcen1WysDbDyg/U01LRgOyp1VzKIOLbsuG7zb8JhnjtrBuF7r4TfT9MFUfMVCDgmXH4WE/GzMxlXVsw/DJl9svBsASB/cRkD9hpHn2GjkB4T7OhuNzuhlNDWFmHV0o1sWluPZdvuqcgvzY3jEA2F+UUwzEvnzHBnJ9BovlIBA/z9Jjgu5k4/fh29e1TyvGlkniitMwpKyug/cix9hgzH8HpjE4p/hQIWCoEg3BZhzYrNrF9dg2XZibnd8s0YaTtsC0X4eUMz8y661e0Io62vZrcQcMz68ovL3OXLT8Hzrf35s9/LaTtyLKXA6y+k9+Ch9BsxkoLiYhBObPJZ60sTMELQVN/C56u2smltPY7tZLWuuUQctZgfDPHb313Px/F1f74CzrpRF0DNbiLgOHdf6k5OBfD8LfyuqIA7paBwR0QcF0JZz570HjSYnv374vF5EEJ9oQIOBSJsWV/HprW1NNUHElHlLmeMVBCxuL+ljT+edSO14I7lvfwuXfA0u6mA47xwC/z0YnhyGntVlvG4aTCBLjRbqVTNJcRTWlVBZa+eVPaupqSiDMMQIJyEqIWyXWudpKh2Acc+K+X+xWcFjFg0N7ZSt207tZsbaK5va+/+2IUZ+JKXHYfGcITLP93Ag1PvI/r3m+CCm2H9Nl3oNHuAgAGeng4n/NE9z79mc3WBl8nCtcZ5CTnLcLvEnwCKyospKiumqKSQgqIC/IVePH4T03Rnt1cIlHKwbRsrYhEJRQm1hWlrCRJoCdK6PUBLcwjHdjNUZMx6nkd0OfGKUGDZvNUW5MLTruMDgL9cBWfcoAubZg8TcDpPTmfvimJu9ZgcDtmHJHYm4mRxKSdV1CqD1SZpImhwJ4BOyUMl8resOQTtOA5bw1HuXrOZ266eQ7D+FXj4RbjoNl3QNHuwgJ+aDr/6Y/vn52/hxEIflxoGIyB3/bhTAXXFxc1UT92B/ZK3j3nkTZbNa4Eg15421Z0F78Fr4LSpuoBpuokF7lPtthkffKr7efp5+McNY5LXwylSMFgISnbUCu9oPXVnxO+aXOodh8XBCLNPupoXAdY9D4//t31md42m27jQALdeCKcfB2Xfcj/PuYzSwX040+vh50IwAkG12NUizlSX3kHxx+q5mxyH5aEID5x4FU/Ft7/ncjhnhi5Umm4s4DizL4ILbmkfsTNzEgV7D+UXXi8/MyRjgQHJqXu+NFc6i8V2FAEF6xyHD0MRHj/xSv4V30cHqTRfOwHHueZMOPs46PPj9nXPzWL/Ij/HmAYHIxgCDJQxMauuB5fycqVTjkMiCBZ04HMUay2LuYEw/zxlCkvj2+jOGJqvvYCT+esNcOJV7Z+vPRP/hFEc7PVwqGkwTsAgBf1Q9JEST5ctcSeutKOwFGzFdZHX2w5LohbvnnsL79bXx+YiQmd31GgB5+SoQ+Duy2D4canr/zaLUV4vYwzBXtJgCNBLQA8UlQrKgRIEhTip844nRgLFmpwQBJSiGWhSikag3lFsU4rPLZtPQhFW/uYqN5qsra1GC3gnefha+O21HdfffxU9qsroaxj0lIJqJOUCylAUConfcTCUcjPXCIEFhByHANDsKBpsmzrLorY5wNYzprldHTUazRfM7Rd/cce+8ET9+2o0XxkjB2b/TimoLNO/kUaj0Wg0Go1Go9FoNBqNRqPRaDQajUaj0Wg0Go1Go9FoNBqNRqPRaDQajUaj0Wg0Go1Go9FoNBqNRqPRaDQajUaj0Wg0Go1Go9FoNBqNRqPRaDQajUaj0Wg0Go1Go9FoNBqNRqPRaDQajUaj0Wg0Go1Go9FoNBqNRqPRaDQajUaj0Wg0Go1Go9FoNBqNRqPRaDQajUaj0Wg0Go1Go9FoNBqNRqPRaDQajUaj0Wg0Go1Go9FoNBqNRqPRaDQajaY78v+HV5sDJhkJqQAAAABJRU5ErkJggg==”>
<azRules>
<rule scope=”dn” constraint=”dc=domain,dc=com”/>
</azRules>
</urls>
<urls label=”Kubernetes Dashboard” url=”/api/v1/proxy/namespaces/kube-system/services/kubernetes-dashboard” name=”kubernetesDashboard” icon=”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”>
<azRules>
<rule scope=”dn” constraint=”dc=domain,dc=com”/>
</azRules>
</urls>
</portal>
<scheduler useDB=”false” threadCount=”3″ instanceLabel=”testing” instanceIPMask=”127″/>
<listeners/>
<reports>

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

Whats Next?

With SSO working great, the next step is to enable Kubernetes’ RBAC model and design a management process around the groups.  Once that model is complete, I can create a dynamic workflow and let users login, request access to Kubernetes resources and let approvers approve that access (plus the usual reports).  Since OpenShift uses the same service account concept as Kubernetes, I think I can use our OpenShift target to be able to pull the annotations our of each namespace (or role, or whatever we build our dynamic workflow on).  Then we’ll have a complete open source identity management solution for Kubernetes.

Thank You

I’d like to say I was smart enough to figure this all out on my own, but anyone who knows me would know thats a lie!  I’d like to thank @ericchiang, @DevoperandI and @whitlockjc on the kubernetes/sig-auth slack channel for helping me out and helping get through some key concepts.

Links

DevOperandi’s integration of KeyCloak and Kubernetes – http://www.devoperandi.com/kubernetes-authentication-openid-connect/

OpenUnison’s OpenID Connect Client – https://github.com/TremoloSecurity/OpenIDConnect/tree/1.0.7

KeyCloak – http://www.keycloak.org/

CoreOS Single Node Kubernetes Vagrant Image – https://coreos.com/kubernetes/docs/latest/kubernetes-on-vagrant-single.html

Marc BoorshteinKubernetes Identity Management – Part I : SSO