tcl logic in SAML Attribute value field possible?

Hi.

We’re running BigIP as a SAML IDP.

Can I somehow issue tcl logic in a SAML attributes?

I’m talking about the Access ›› Federation : SAML Identity Provider : Local IdP Services, editing an object, under SAML Attributes.
Based on what’s in the memberOf attribute, I need to issue as a value either empty string or “SpecificValue”.

I am familiar with the %{session.variable} construct, but I don’t want to clutter the session with more variables if I can avoid it, as that impacts all sessions using our IDP (30 or so federated services on the same VIP and AP).

I tried these two approches:
%{ set result {} ; if { [mcget {session.ad.last.attr.memberOf}] contains {| CN=SpecificGroup,OU=Resource groups,OU=Groups,DC=Domain,DC=com |}} { set result {SpecificValue} } ; return $result }

expr { set result {} ; if { [mcget {session.ad.last.attr.memberOf}] contains {| CN=SpecificGroup,OU=Resource groups,OU=Groups,DC=Domain,DC=com |}} { set result {SpecificValue} } ; return $result }

Expected result:
An issued claim with the value “” or “SpecificValue”

Actual result:
An issued claim with the above code as the value

As I mentioned, we’ve set it up using one VIP that is hosting 30 or so services. We’re running 16.1.3.1.
They are using the same SSO configuration and there’s an iRule triggerd at ACCESS_POLICY_AGENT_EVENT, which does some magic to extract issuer and suchlike, and that helps to make decisions later in the Access Policy.
It also populates a few session variables under the session.custom namespace for use in the Access Policy.
Additional session variables are being populated in the Access Policy, such as resolved manager and their email address.
I have looked briefly at the ASSERT::saml functions, but even if it would be possible to manipulate that way, I wish to keep this set up as stream lined as possible and with as few new “special cases” in an iRule. So while I appreciate pointers along that route as well, I would first of all like to know if there is a way to do it natively in the SAML attribute value field.

And if there are any options I have not yet explored here?

Hi Samuel,

It would honestly make me wonder if you could write native TCL syntax into these fields.

You will most likely be forced to build the Role value within VPE. ASSERT::saml would be a choice to, but VPE makes somehow more sense for this static Role=Group match assertion thingy…

If nobody else responding to this question and bringing up some insights, you may contact F5 support to get sure. Please leave us a line what the outcome was… :wink:

Cheers, Kai

I would probably suggest using the Empty Agent’s branch matching capabilities inside of a macro to set the attribute based on group matching like that. It is pretty capable for that feature.

Something like this:

Thank you for your inquiry.

Your solution would work.

However, it’s not a route I want to take, our VPE is already cluttered enough, with ten or so fairly complex macros. I’m trying to keep it as general as I possibly can, and put the complexity in either iRules or - as I really wanted to do this time - in the actual SAML attribute value.
If I can’t have it my way, I’m still undecided whether I’ll go the iRule route or the VPE route.

I’ll keep you guys posted. :slightly_smiling_face:

Thanks Kai,

I will contact F5 support to make sure, perhaps whine a little if it’s not supported, and report back here with my findings.

Do this. A little whine helps a lot… :wink:

Cheers, Kai

Did you end up figuring out a solution? I am also in the same boat. Empty branch rules dont work for me because I might have someone that has belongs to two of the groups and needs to access both. An irule would just clutter everyone with a ton of variables. Being able to assign a default in the saml variable value field would be awesome.

Yes, I have, actually. And I’m happy to share. :slightly_smiling_face:
In short, I have normalized a session variable scope shared along all SAML2 paths, regardless of the data source, under session.custom.global.* (such as username, title, email address, manager’s email address, department etc)

For cases I can’t solve using only the data sources, I have a second namespace which is also always populated: session.custom.workarounds.*

Then, as the last step in every successful path of the VPE, I “apply workarounds”, a macro doing only one thing:

WORKAROUNDS is implemented in the same iRule providing the regular ACCESS_POLICY_AGENT_EVENT as such:

when ACCESS_POLICY_AGENT_EVENT {
	# SAML agent
	if { [ACCESS::policy agent_id] eq "SAML agent" } {
		# ...
	}
	# ...

	# WORKAROUNDS

	if { [ACCESS::policy agent_id] eq "WORKAROUNDS" } {
		# set some globals
		ACCESS::session data set {session.custom.global.country} [ACCESS::session data get {session.ad.last.attr.co}]
		ACCESS::session data set {session.custom.global.country2} [ACCESS::session data get {session.ad.last.attr.c}]
		ACCESS::session data set {session.custom.global.locality} [ACCESS::session data get {session.ad.last.attr.l}]
		ACCESS::session data set {session.custom.global.streetAddress} [ACCESS::session data get {session.ad.last.attr.streetAddress}]
		ACCESS::session data set {session.custom.global.postalCode} [ACCESS::session data get {session.ad.last.attr.postalCode}]

		# [anonymized system]
		set workaround_anonsystem {}
		if { [ACCESS::session data get {session.ad.last.attr.memberOf}] contains {| CN=SpecificGroup,OU=Resource groups,OU=Groups,DC=Domain,DC=com |}} { set workaround_anonsystem {AnonSystem_User} }
		ACCESS::session data set session.custom.workarounds.anonsystem_role $workaround_anonsystem

		# employee count (non-zero indicates manager)
		if { [ACCESS::session data get {session.ldap.last.queryresult} ] == 1 } {
			ACCESS::session data set {session.custom.global.minionCount} [ACCESS::session data get {session.ldap.last.totalEntries}]
		}

		# Another App workaround for exposed groups
		set workaround_anotherapp_role {}
		if { [ACCESS::session data get {session.ad.last.attr.memberOf}] contains {| CN=anotherapp-user,OU=Resource groups,OU=Groups,DC=Domain,DC=com |}} { set workaround_anotherapp_role {anotherapp-user} }
		if { [ACCESS::session data get {session.ad.last.attr.memberOf}] contains {| CN=anotherapp-superuser,OU=Resource groups,OU=Groups,DC=Domain,DC=com |}} { set workaround_anotherapp_role {anotherapp-superuser} }
		ACCESS::session data set session.custom.workarounds.anotherapp_role $workaround_anotherapp_role
	}
}

And now my original problem has been reduced to:

The solution does increase the off-boarding process a tiny bit, when a service is decommissioned.

But containing complexity and workarounds to this one section in this one iRule, and keep everything else simple, is a no-brainer trade-off to us.

I sincerely hope this will help you, and anyone else.

Cheers
/Sam

@Samuel_Rydén Thank you for coming back to this post to share your update!