on 16-Jan-2017 06:36
Problem this snippet solves:
Second Factor Only authentication allows a SP to authenticate only the second factor of a user. With SFO you can add two factor authentication to your institutions application gateway (e.g. Citrix Netscaler or F5 BIG-IP) or to the authentication or authorization gateway (e.g. Microsoft ADFS or Novell/NetIQ). SFO has its own authentication endpoint at SURFsecureID (SURFconext Strong Authentication has been renamed to SURFsecureID).
More information about SURFsecureID SFO
Currently the last requirement (#4) isn't supported within APM (v12.1.1 HF2). But with use of iRules LX a workaround can be build.
From BIG-IP version 14.1.0 and higher it's possible to manipulate SAML requests and responses with use of SAML related iRule commands and events. These features makes it very simple apply a workaround.
See: https://github.com/nvansluis/f5.surfsecureid
To work around the current limitations of BIG-IP APM we need build two virtual servers. I'll call them 'SURFconext_SP_frontend' and 'SURFconext_SP_backend'. The 'SURFconext_SP_backend' is the virtual server that is doing the actual authentication. This server holds the Access Policy. The 'SURFconext_SP_frontend' will be the virtual server that modifies the SAML request that is build by the backend virtual server. The 'SURFconext_SP_frontend' virtual server makes use of iRules LX to do this.
# /var/ilx/workspaces/Common/SURFconext_SFO_workspace/extensions/SURFconext_SFO_extension/node_modules
# npm install pako urlencode string crypto --save
crypto@0.0.3 crypto
string@3.3.3 string
urlencode@1.1.0 urlencode
└── iconv-lite@0.4.15
pako@1.0.4 pako
#
How to use this snippet:
when HTTP_REQUEST {
virtual SURFconext_SP_backend
}
when HTTP_RESPONSE {
set SSOServiceUrl "https://gateway.pilot.stepup.surfconext.nl/second-factor-only/single-sign-on"
set SURFconextIdentifier "urn:collab:person:some-organisation.example.org"
set PrivateKey [ifile get "/Common/surfconext_sp_key"]
if { [HTTP::header value Location] starts_with $SSOServiceUrl } {
# set SURFconextIdentifier
set UserID "[ACCESS::session data get session.logon.last.username]"
set SURFconextIdentifier "${SURFconextIdentifier}:${UserID}"
# extract SAMLRequest and SigAlg parameters
set location_url [HTTP::header value Location]
set SAMLRequest [URI::query $location_url "SAMLRequest"]
set SigAlg [URI::query $location_url "SigAlg"]
# create new SAMLRequest with ILX
set rpc_handle [ILX::init SURFconext_SFO_plugin SURFconext_SFO_extension]
if {[catch {ILX::call $rpc_handle createNewSAMLRequest $SAMLRequest $SURFconextIdentifier $PrivateKey $SigAlg} newURI]} {
log local0.error "Client - [IP::client_addr], ILX failure: $newURI"
# Send user graceful error message, then exit event
HTTP::respond 400 content "<html>Error. Failed to create SAML request.</html>"
return
}
# send new SAMLRequest
HTTP::header replace Location "$SSOServiceUrl?$newURI"
}
}
'use strict';
// Import the Node.js modules
var objF5 = require('f5-nodejs');
var pako = require('pako');
var urlencode = require('urlencode');
var string = require('string');
var crypto = require('crypto');
// Create a new RPC server for listening to TCL iRule calls
var objILX = new objF5.ILXServer();
// Create method
objILX.addMethod('createNewSAMLRequest', function(objArgs, objResponse) {
var SAMLRequest = objArgs.params()[0];
var SURFconextIdentifier = objArgs.params()[1];
var PrivateKey = objArgs.params()[2];
var SigAlg_encoded = objArgs.params()[3];
// decode and inflate AuthnRequest
var SAMLRequest_decoded = new Buffer(urlencode.decode(SAMLRequest), 'base64');
var SAMLRequest_inflated = pako.inflateRaw(SAMLRequest_decoded, {to:'string'});
// build Subject element
var subject = '<saml:Subject xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion"><saml:NameID Format="urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified">' + SURFconextIdentifier + '</saml:NameID></saml:Subject>';
// rebuild AuthnRequest
var firstPart = string(SAMLRequest_inflated).between('','<samlp:NameIDPolicy');
var lastPart = string(SAMLRequest_inflated).between('<samlp:NameIDPolicy');
var newSAMLRequest = firstPart + subject + "<samlp:NameIDPolicy" + lastPart;
// deflate and encode modified AuthnRequest
var newSAMLRequest_deflated = new Buffer(pako.deflateRaw(newSAMLRequest));
var newSAMLRequest_encoded = urlencode.encode(newSAMLRequest_deflated.toString('base64'));
// create new signature
var newURI = "SAMLRequest=" + newSAMLRequest_encoded + "&SigAlg=" + SigAlg_encoded;
var key = PrivateKey.toString('ascii');
var sign = crypto.createSign('RSA-SHA256');
sign.update(newURI);
var signature = urlencode.encode(sign.sign(key,'base64'));
// create new URI
newURI += "&Signature=" + signature;
// return AuthnRequest to Tcl iRule
objResponse.reply(newURI);
});
// Start listening for ILX::call events
objILX.listen();
Code :
72843
Could someone explain the need for two virtuals and how/why the first calls the second? In my case I have built a portal which provides webtop links and portal links to internal apps. I am trying to use RSA for SFO Authentication. The requirements are exactly as described by this snippet which is the inclusion of the user name in the Subject element of the AuthnRequest.
Using the terminology in this article, I believe my existing Portal which has the access policy, performs the primary authentication and ultimately presents the portal links is the backend server. I am not sure exactly how the front-end virtual is supposed to be defined, addressed and called. Why can the iRules not be assigned to the main portal virtual?
Any additional details that could be provided would be helpful. Thanks.
APM 12.1.2
The frontend virtual server is kind of a wrapper for the virtual server that holds the actual access policy. The reason why this extra virtual server is needed has to do with the internal working of the SAML process that is performed by the access policy. This process will not trigger the HTTP_RESPONSE iRule event, which makes it impossible to intercept and alter the SAML request. However when using this layered virtual server structure, the frontend virtual server that is logically between the backend virtual server and the IDP will trigger the HTTP_RESPONSE iRule event and makes it possible to intercept and alter the SAML request.
I hope this clarifies the need for an extra virtual server.