Hub-and-spoke support in Moonshot

Still being written

Introduction

Mesh federations are widely used in the federation context, such as eduGAIN's SAML confederation. Moonshot is designed to be used in this point-to-point fashion, in which every RP proxy can theoretically be connected to every identity provider in a Moonshot federation. 

Other federation methodologies exist, notably the hub-and-spoke model, in which either only one identity provider is exposed to any service within the federation or in the confederation that the home federation is part of. Another use of the hub-and-spoke model is in use amongst existing research e-infrastructures across the world, where a service bridges two federations whilst appearing as a service to the one federation, and as identity provider to the other.

In SAML federations the implementation of hub-and-spoke is conceptually not difficult as the authentication flow is driven interactively by the user, but non-interactive authentication flows require hints to tell the actors in the authentication flow what to do next.

RFC 7542

RFC 7542 specifies a network access identifier (NAI) format that allows for inter-domain user identification. Currently, the Moonshot Identity Selector and the Moonshot mechanism do not support this format, but the project aims to add this support as soon as possible.

How does it work?

Here is a broad flow of steps how Moonshot authentication flows work with RFC 7542.

Actors

A Service connected to RP Proxy 'service.realm'.

A Proxy Service at 'intermediate.realm'.

An IDP at 'home.realm'.

The flow

  1. A Moonshot-enabled service at 'service.realm' receives a request for 'home.realm!@intermediate.realm'.
  2. The RP Proxy for 'service.realm' does an RFC7542-style check to see if either 'home.realm' or 'intermediate.realm' are local to it. They are not.
  3. The RP Proxy does an ABFAB lookup to find '@intermediate.realm', and assuming it is allowed to, connects to the declared IDP for 'intermediate.realm', i.e. the Proxy Service.
  4. The Proxy Service at 'intermediate.realm' receives the request for 'home.realm!@intermediate.realm'.
  5. The Proxy Service does an RFC7542-style check to see if either 'home.realm' or 'intermediate.realm' are local. 'intermediate.realm' is.
  6. The Proxy Service then checks if 'home.realm' is local. It is not.
  7. The Proxy Service does an ABFAB lookup to find '@home.realm', and assuming it is allowed to, connects to the declared IDP for 'home.realm'.
  8. The IDP for 'home.realm' receives the request for 'home.realm!@intermediate.realm'.
  9. The IDP does an RFC7542-style check to see if either 'home.realm' or 'intermediate.realm' are local. 'home.realm' is.
  10. The IDP ignores 'intermediate.realm' and does authentication on 'home.realm'.
  11. The IDP returns the authentication result to the Proxy Service, because it was the service that connected to it.
  12. The Proxy Service receives the authentication result, decorates it with more information and returns the authentication result to the RP Proxy at 'service.realm'.
  13. The 'service.realm' RP Proxy deals with the result, and passes on the accept or reject to the original service.

Pre-conditions for RFC 7542 routing

  1. All actors in this style of routing must support RFC 7542.
  2. The Proxy Service at 'intermediate.realm' must declare in the Trust Router configuration (the Assent Service) that 'service.realm' is allowed to claim to be part of 'intermediate.realm'. In EGI/ELIXIR-style environments, this is acceptable, as they are all part of the infrastructure.
  3. The IDP at 'home.realm' must prioritise 'home.realm' over 'intermediate.realm' (left to right)
  4. The Proxy Service must prioritise 'intermediate.realm' over 'home.realm' (right to left).

Information disclosure

Both 'service.realm' and 'intermediate.realm' will know that the user will be from 'home.realm' with an RFC 7542 NAI. However, the actual credential (User-Name) in the EAP tunnel will continue to only be known by 'home.realm'. Also, given that ABFAB routing relies on knowing the home realm of a user, this style of routing should not be a problem.

The IDP at 'home.realm' will also know that the service at 'service.realm' originated the request, despite receiving the request from 'intermediate.realm'. This may not be acceptable to the operator(s) of 'intermediate.realm' or 'service.realm'. The positions of either operators is to be clarified.

RFC7542 policy file

Save this code block as 'rfc7542' and store it in the policy.d directory of your FreeRADIUS installation. Replace '${changeme}' with your declared home realm. 

Then comment out the 'suffix { }' block in your abfab-tr-idp file's authorize section, and add the line 'rfc7542'. Also replace the line 'suffix' in the inner-tunnel file's authorize section with the line 'rfc7542'.

rfc7542 in policy.d
#
#  The following policy is for RFC7542-style suffix 
#  management.
#  
#  It partially replaces the standard 'suffix' use by 
#  enhancing the logic short of rewriting suffix entirely.
#
#	$Id$
#
#  This is your local proxy domain. Sadly we can't read this 
#  from the proxy.conf file. 
#
rfc7542_suffix = ${changeme}

#  This policy checks the User-Name attribute whether it is in
#  RFC7542 format. If it is, it temporarily rewrites it for use 
#  by the 'suffix' module, then restores the original afterwards
# 
rfc7542.authorize {
	#
	#  Check whether the User-Name is in the RFC7542 format. If so, we'll need to do some special stuff
	#
	if (&request:User-Name =~ /([a-zA-Z0-9\.-]+)!([a-zA-Z0-9\.-]*)\@(.+)/) {
		#  Store the three parts of the User-Name, store the original User-Name too
		update control {
			Tmp-String-0 := &request:User-Name
		}
		#  Format: not_local_realm!...@local_realm: Rewrite User-Name for suffix
		if (("%{1}" != "${policy.rfc7542_suffix}") && ("%{3}" == "${policy.rfc7542_suffix}")) {
			update request {
				User-Name := "%{2}@%{1}"
			}
		}
		#  Format: local_realm!...@not_local_realm: Rewrite User-Name for suffix
		if (("%{1}" == "${policy.rfc7542_suffix}") && ("%{3}" != "${policy.rfc7542_suffix}")) {
			update request {
				User-Name := "%{2}@%{1}"
			}
		}
	}
	suffix {
		updated = 1
		noop = reject
	}
	#  Restore the User-Name to its original glory.
	if (&control:Tmp-String-0 && (&request:User-Name != &control:Tmp-String-0)) {
		update request {
			User-Name := &control:Tmp-String-0
		}
		update control {
			Tmp-String-0 !* ANY
		}
	}
}