Using Check Object password for authentication

0 Likes
If you have ever worked with a SOAP or REST driver you will know that they support both sending messages (SOAP usually as XML, or REST as JSON or XML) but also that they support listening for incoming events. Thus you can set up your IDM SOAP or REST driver to be a listening endpoint for a service.

Once you do this one of the things you need to be concerned about is about authentication. Outbound, the shims have some built in authentication methods. Basic Authentication for SOAP, which consists of a header that has the username and password, separated by a colon and then base 64 encoded.

Inbound the SOAP shim supports the same approach. You specify the username and password in the configuration as to what an allowed username and password is allowed.

However you might want more flexibility than that. Or even more security than that.

Recently I was working with Workday.com's web services and they use a different approach for security. A major weakness in the header based Basic Authentication is that the header is not encrypted inside the SSL session. Thus while you are making an HTTPS connection, and passing a username and password, it is not inside the secure session, it is outside it.

Instead, Workday uses something called WSS (Web Services Security) which is pretty simple in concept, inside the SOAP Header node they insert a wsse:Security node, which has the username and password. Now this seems backwards as it is now visible in the trace, whereas before in the header you had to at least retrieve the header, and decode the values, now it is clear text. But by being inside the SOAP message, the contents are now encrypted by the SSL session.

Outbound, sending messages to Workday from the SOAP driver, this is pretty easy. Decide how you will get the username and password from your palette of options. You can use a GCV for the name, a Named Password. You can specify a user DN in a GCV or hard coded, and then read the nspmDistributionPassword. You can just hard code the values in the clear in policy.

Once you know the username and password, it is a matter of Append XML Element, Append XML Text, and Set XML Attribute operations to add the proper nodes to your document. Or of course you could use XSLT (Icky! Not my favorite, policy suffices!).

This is all standard SOAP driver stuff. Decide how you plan on converting XDS documents to SOAP and add in this set of nodes. The standard way is XSLT transform. I suggested using Policy to do the whole thing for an example of Salesforce.com years before the NetIQ driver was available. My boss came up with a much more clever model he calls LiQUiD SOAP (Lightweight, Quick Development SOAP) where we have a package you add on to the SOAP driver deployment and it reads DirXML-Resource files that define how to map an element in the XDS to SOAP and vica versa on the way back.

We have used this to develop drivers for Blackboard LMS, Cisco Unified Call Manager, Workday, PaperCut (Print account package, amusingly not using SOAP but XML-RPC), and more.

We needed an add on for WSS support, and outbound was pretty easy. Just append the XML as needed into the SOAPENV:Header node as needed, getting the values from GCV's. Alas, cannot easily use the Driver authentication information, since it is easy to read the username (Get the Source Attribute DirXML-ShimAuthID) but the password is stored as DirXML-ShimAuthPassword and is not quite retrievable. At least, I do not know how to get it. The Driver object password is the actual objects password. The Remote and Application Passwords I think are stored the similar to the model used for Named Passwords but are not retrievable that way.

That is why you will see that the default configuration for the SOAP driver packages use SubAuth-1 and SubAuthPwd-1 style username and password GCV options to make getting the values easier in Policy.

Of course we could have stored the password on an eDirectory user object, and read it when we needed it. Both ways work equally well.

Inbound however, we have a different problem. The SOAP Shim only supports inbound Basic Authentication and Workday does not send that kind of Authentication.

When you register your Subscription to a series of Events with Workday, you tell Workday the URL of your listening endpoint, the username and password to send for authentication, as well as all the events for which you wish to listen.

Up until now I have been using a simple username, but it just hit me as I write this, that the field is just a text string and nothing stops you from using an LDAP DN, a dotted DN or whatever format you want.

Regardless, the incoming header has the following nodes (selected as XPATH for my example):
soapenv:Header/wsse:Security/wsse:UsernameToken/wsse:Username
soapenv:Header/wsse:Security/wsse:UsernameToken/wsse:Password


Getting the values out into a variable is easy using XPATH as above (Do remember to declare your name spaces in the policies). Once you have them in variables (remember to No Trace the password variable, since you would like to prevent it from appearing in trace), you can compare the username to a GCV or hard coded string, same for a Named password. However, I wanted to do something a bit more clever and a bit more IDM style.

You may be familiar with the standard IDM events:
add
modify
modify-password
delete
rename
move
sync
query
query-ex
query-schema
instance
status
trigger
add-association
remove-association
modify-association

But if you ever hit the magnifying glass in Designer on a If Operation token, you will see there are several more events:

check-password
check-object-password
generated-password
get-named-password
init-params

There are many interesting operations in there that we commonly do not use. One minor side note. If you have a Veto in your Input or Output transform, that veto's without checking the operation or object class, you can end up vetoing the driver startup, which uses an init-param operation to pass connection, authentication, and filter information. Unconstrained Vetos can be bad as you might imagine.

While we often see add-association, modify-association is not often seen and it has two association nodes, the old and the new one. (The LDAP driver sends this on a move, since the DN is the association value, so when you move a user its association changes as well).

You likely have seen the check-password during driver startup with a message that this is not an error it is just checking the Driver object password is not blank. That is a shim checking to see that any password is set on the driver object. Beyond Remote Loader authentication, I am not entirely certain why to bother with that check at all, but some drivers do send it.

However there is another check password operation, check-object-password which is kind of neat.

When you use iManager to check password synchronization status of a user in different systems, it sends a <check-object-password> with the value of nspmDistributionPassword into the target driver flow, and the response is either a <status level="success"> or an error that password does not match.

Successful response to a check-object-password operation:

[09/27/17 19:59:48.055]:wd-not PT:
<nds dtdversion="4.0" ndsversion="8.x">
<source>
<product edition="Advanced" version="4.5.5.0">DirXML</product>
<contact>NetIQ Corporation</contact>
</source>
<output>
<status level="success"><application>DirXML</application>
<module>wd-notification</module>
<object-dn></object-dn>
<component>Publisher</component>
</status>
</output>
</nds>


Failure response to a check-object-password event.

<nds dtdversion="4.0" ndsversion="8.x">
<source>
<product edition="Advanced" version="4.5.5.0">DirXML</product>
<contact>NetIQ Corporation</contact>
</source>
<output>
<status level="error">Code(-9046) Invalid password specified for <check-object-password>.<application>DirXML</application>
<module>wd-notification</module>
<object-dn></object-dn>
<component>Publisher</component>
</status>
</output>
</nds>


The operation is pretty simple, inside the <nds><input> envelope, it looks like:
	<check-object-password dest-dn="o\ou\CN">
<password>SomePassword</password>
</check-object-password>


What is nice is that the engine knows to protect the contents of the <password> node as though you had flagged it with the XML attribute of is-sensitive set to true. In fact, when you build your outbound response, as you Append XML Element the wsse:Password node, before you Append XML Text the password into, first Set XML Attribute is-sensitive to true, and then the password will not show up in trace as it flows through the engine.

You would think the extra element with break Workday, but it turns out not to care, and the shim does not change the text of the XML rather it just uses the flag to hide the value in trace, so you send the proper document to Workday (Plus one extra XML attribute) and all is well.

But because <password> nodes are treated special by the engine, no extra work to hide it is required.

Thus once you have taken out the Username from the inbound SOAP header's appropriate node, and decided how to find that user and get its DN you can make this call.

You could Query for it, into a node set variable say QUERY, then use XPATH to get the DN as $QUERY/@src-dn and then you have the src-dn.

You could have configured Workday's subscription to specify the o\ou\cn format and just use the value passed in, in its entirety.

Where a search approach has merit is when you have multiple possible inbound users, and since you can look for each one in a specific location, and then check their password you can support more than just a single user.

Ok, all that is fine, but how do you execute a check-object-password event? There is no token for it after all. The trick is to build the XDS document in a variable, I usually store the bulk of it in a GCV, and then use the Destination Command Processor to submit it to the engine for processing.

To do this, you need only one major trick, which is to define the XML Namespace for cmd, so that you can execute the event.

Here is a sample rule that accomplished most of what is needed.

<rule>
<description>[WSS] Validate password - check object password</description>
<comment/>
<comment name="author" xml:space="preserve">Geoffrey Carman</comment>
<comment name="version" xml:space="preserve">1</comment>
<comment name="lastchanged" xml:space="preserve">Sept 27, 2017</comment>
<conditions>
<and>
<if-global-variable mode="nocase" name="cis.ls.wss.operating-mode" op="equal">check-object-password</if-global-variable>
<if-xpath op="true">descendant-or-self::soapenv:Header/wsse:Security</if-xpath>
</and>
</conditions>
<actions>
<do-set-local-variable name="USERNAME" scope="policy">
<arg-string>
<token-xpath expression="descendant-or-self::soapenv:Header/wsse:Security/wsse:UsernameToken/wsse:Username/text()"/>
</arg-string>
</do-set-local-variable>
<do-set-local-variable name="PASSWORD" scope="policy">
<arg-string>
<token-xpath expression="descendant-or-self::soapenv:Header/wsse:Security/wsse:UsernameToken/wsse:Password/text()"/>
</arg-string>
</do-set-local-variable>
<do-set-local-variable name="AUTH-USER" scope="policy">
<arg-node-set>
<token-query class-name="User">
<arg-match-attr name="CN">
<arg-value type="string">
<token-local-variable name="USERNAME"/>
</arg-value>
</arg-match-attr>
</token-query>
</arg-node-set>
</do-set-local-variable>
<do-if>
<arg-conditions>
<and>
<if-xpath op="true">$AUTH-USER/@src-dn</if-xpath>
</and>
</arg-conditions>
<arg-actions>
<do-set-local-variable name="AUTH-USER-DN" scope="driver">
<arg-string>
<token-xpath expression="$AUTH-USER/@src-dn"/>
</arg-string>
</do-set-local-variable>
<do-set-local-variable name="DEST-CMD-DOC" scope="driver">
<arg-node-set>
<token-xml-parse>
<token-global-variable name="cis.ls.wss.check-object-password-xml"/>
</token-xml-parse>
</arg-node-set>
</do-set-local-variable>
<do-set-xml-attr expression="$DEST-CMD-DOC/nds/input/check-object-password" name="dest-dn">
<arg-string>
<token-local-variable name="AUTH-USER-DN"/>
</arg-string>
</do-set-xml-attr>
</arg-actions>
<arg-actions/>
</do-if>
<do-append-xml-text expression="$DEST-CMD-DOC/nds/input/check-object-password/password">
<arg-string>
<token-local-variable name="PASSWORD"/>
</arg-string>
</do-append-xml-text>
<do-set-local-variable name="CHECK-PASSWORD" scope="policy">
<arg-node-set>
<token-xpath expression="cmd:execute($destCommandProcessor,$DEST-CMD-DOC )"/>
</arg-node-set>
</do-set-local-variable>
<do-if>
<arg-conditions>
<and>
<if-xpath op="not-true">$CHECK-PASSWORD/nds/output/status/@level='success'</if-xpath>
</and>
</arg-conditions>
<arg-actions>
<do-trace-message level="1">
<arg-string>
<token-text xml:space="preserve">Invalid auth attempt. Username: </token-text>
<token-local-variable name="USERNAME"/>
<token-text xml:space="preserve">
DN: </token-text>
<token-local-variable name="AUTH-USER-DN"/>
<token-text xml:space="preserve">
Password provided: </token-text>
<token-local-variable name="PASSWORD"/>
</arg-string>
</do-trace-message>
<do-veto/>
</arg-actions>
<arg-actions/>
</do-if>
</actions>
</rule>


First you have conditions that check a GCV to see if we should even use this mode. Always a good idea. This lets you turn it off and on fairly easily as you test. Then it detects the wsse:Security node, which would indicate there is information to process.

Next you need to get the Username and Password nodes from the event into variables. You should probably no-trace the local variable set for the Password once you have it working properly to protect it from showing in the trace.

Once you have the username, assuming you configured Workday with just the username and not the full DN (since it is only a string on that side) there is a need to find the object. I left the query for AUTH-USER as starting at the root of the data store, but you could of course limit it to a subtree for these accounts.

The AUTH-USER variable is a nodeset, which means you can use XPATH against it, and the easiest way to see if you got a result is to test for an XML attribute src-dn. Thus if XPATH is true, $AUTH-USER/@src-dn it means we got a result and a src-dn we can use.

Now that we have the proper IDM formatted DN, we get the GCV with the XML framework of the check object password event as a nodeset, so instead of doing text replaces, we can use XML actions upon it.

I have used this approach in the JDBC driver to send SQL commands easily. In that case, I think I stored it as a string, and used Replace All tokens to replace known placeholders with my data. This time I decided to do it with the Set XML Attribute and Add XML Text tokens. Just for variety.

The things we need are the dest-dn XML attribute to specify the target object, and of course the value of the password in the <password> node.

Finally, using XPATH, in a set local variable, you cmd:execute the document, and the result is stored in the variable.

As noted above there is a success and error case, so in the case of an error, Trace out a message at a low trace level (Like 1 or 0) and then veto the event, since you do not wish unauthorized users to be able to send in events.

Labels:

How To-Best Practice
Comment List
Related
Recommended