BIG-IP iRulesLX FakeADFS - WS-Federation/SAML11

Details
This was created as a solution to REPLACE the need for AD FS to tie APM into SharePoint.  The goal was originally to demonstrate the flexibility of iRulesLX and also to find a way to add WS-Federation support very quickly.  This solution is currently SP initiated, but wouldn't take much to handle IdP initiated as well.  

An example of the process flow is detailed in the following image. 

All that's needed for this solution is to create or import the iRulesLX workspace, configure SharePoint as if you are connecting to AD FS as a trusted Identity Provider, but point to a virtual server on the BIG-IP.

Instructions
 
First, ensure that you have a certificate / key for the IDP, as well as the CA Chain/Root CA.  These do not have to be from the same domain CA that SharePoint lives on, if it exists.  We are going to add these to SharePoint as trusted.
 
On one of the SharePoint servers, open the SharePoint Management Powershell, issue the following.
 
To import the Trusted Root CA, that issued the signing cert:

$root = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2("")
New-SPTrustedRootAuthority -Name "Token Signing Cert Parent" -Certificate $root

To import the Trusted Certificate

$cert = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2("")

Next, create the Claim mappings for SharePoint:

$emailClaimMap = New-SPClaimTypeMapping -IncomingClaimType "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress  
" -IncomingClaimTypeDisplayName "EmailAddress" –SameAsIncoming
$upnClaimMap = New-SPClaimTypeMapping -IncomingClaimType "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/upn  
" -IncomingClaimTypeDisplayName "UPN" –SameAsIncoming
$roleClaimMap = New-SPClaimTypeMapping -IncomingClaimType "http://schemas.microsoft.com/ws/2008/06/identity/claims/role  
" -IncomingClaimTypeDisplayName "Role" –SameAsIncoming
$sidClaimMap = New-SPClaimTypeMapping -IncomingClaimType "http://schemas.microsoft.com/ws/2008/06/identity/claims/primarysid  
" -IncomingClaimTypeDisplayName "SID" –SameAsIncoming

 
Note:  Additional claim options here.

Next, create the Trusted Identity Provider.

$realm = "urn:sharepoint:"
$signInURL = "https:///adfs/ls"
$ap = New-SPTrustedIdentityTokenIssuer -Name (ProviderName) -Description (ProviderDescription) -realm  
$realm -ImportTrustCertificate $cert -ClaimsMappings $emailClaimMap,$upnClaimMap,$roleClaimMap,$sidClaimMap  
-SignInUrl $signInURL -IdentifierClaim $emailClaimmap.InputClaimType

To Associate a SharePoint Web Application with the new Identity Provider.

To configure an existing web application to use the FakeADFS identity provider
Verify that the user account that is performing this procedure is a member of the Farm Administrators SharePoint group.

  1. In Central Administration, on the home page, click Application Management.
  2. On the Application Management page, in the Web Applications section, click Manage web applications.
  3. Click the appropriate web application.
  4. From the ribbon, click Authentication Providers.
  5. Under Zone, click the name of the zone. For example, Default.
  6. On the Edit Authentication page in the Claims Authentication Types section, select Trusted Identity provider, and then click the name of your SAML provider (<ProviderName> from the New-SPTrustedIdentityTokenIssuer command). Click OK.

Next, you must enable SSL for this web application. You can do this by adding an alternate access mapping for the “https://” version of the web application’s URL and then configuring the web site in the Internet Information Services (IIS) Manager console for an HTTPS binding. 

APM Policy Flow

The current workflow works as follows:
    1.  A user opens a link to SharePoint.
    2.  SharePoint presents Authentication options for the WebApp.

     3.  A user selects Authentication Source (FakeADFS), enters credentials, which then redirects the user to Virtual Server configured with FakeADFS iRulesLX solution.
       4.  FakeADFS Generates a SAML11 assertion, then wraps it in the WS-Federation elements.

<t:RequestSecurityTokenResponse xmlns:t="http://schemas.xmlsoap.org/ws/2005/02/trust">  
<t:Lifetime>  
  <wsu:Created xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">2016-12-20T13:56:01.349Z</wsu:Created>  
  <wsu:Expires xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">2016-12-20T14:56:01.349Z</wsu:Expires>  
</t:Lifetime>  
 <wsp:AppliesTo xmlns:wsp="http://schemas.xmlsoap.org/ws/2004/09/policy">  
  <wsa:EndpointReference xmlns:wsa="http://www.w3.org/2005/08/addressing">  
  <wsa:Address>urn:sharepoint:f5test</wsa:Address>  
  </wsa:EndpointReference>  
</wsp:AppliesTo>  
<t:RequestedSecurityToken>  
 <saml:Assertion xmlns:saml="urn:oasis:names:tc:SAML:1.0:assertion" MajorVersion="1" MinorVersion="1"   
AssertionID="_8Ed1lOeqLhnGTETTOn9BHJn72TZ8lE25" IssueInstant="2016-12-20T13:56:01.363Z" Issuer="http://fakeadfs.f5ttest.com/adfs/services/trust">  
<saml:Conditions NotBefore="2016-12-20T13:56:01.363Z" NotOnOrAfter="2016-12-20T14:56:01.363Z">  
  <saml:AudienceRestrictionCondition>  
  <saml:Audience>urn:sharepoint:f5test</saml:Audience>  
  </saml:AudienceRestrictionCondition>  
</saml:Conditions>  
<saml:AttributeStatement>  
<saml:Subject>  
<saml:SubjectConfirmation>  
<saml:ConfirmationMethod>urn:oasis:names:tc:SAML:1.0:cm:bearer</saml:ConfirmationMethod>  
</saml:SubjectConfirmation>  
</saml:Subject>  
<saml:Attribute AttributeNamespace="http://schemas.xmlsoap.org/ws/2005/05/identity/claims" AttributeName="emailaddress">  
<saml:AttributeValue>flip@f5test.com</saml:AttributeValue>  
</saml:Attribute>  
<saml:Attribute AttributeNamespace="http://schemas.xmlsoap.org/ws/2005/05/identity/claims" AttributeName="upn">  
<saml:AttributeValue>0867530901@f5test.com</saml:AttributeValue>  
</saml:Attribute>  
</saml:AttributeStatement>  
<saml:AuthenticationStatement AuthenticationMethod="urn:oasis:names:tc:SAML:1.0:am:password" AuthenticationInstant="2016-12-20T14:56:01.363Z">  
<saml:Subject>  
<saml:ConfirmationMethod>urn:oasis:names:tc:SAML:1.0:cm:bearer</saml:ConfirmationMethod>  
</saml:SubjectConfirmation>  
</saml:Subject>  
</saml:AuthenticationStatement>  
<Signature xmlns="http://www.w3.org/2000/09/xmldsig#">  
<SignedInfo>  
<CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>  
<SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/>  
<Reference URI="#_8Ed1lOeqLhnGTETTOn9BHJn72TZ8lE25">  
<Transforms>  
<Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature"/>  
<Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>  
</Transforms>  
<DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>  
<DigestValue>fqLP9yVFDNveaOWwyEVl2Bc9M6bEzKb7KMZ2x33VgUc=</DigestValue>  
</Reference>  
</SignedInfo>  
<SignatureValue>tSLsHUu5m1Mc7qNmdfa5daEK2+noAgbuZ5faGaXQw7qCPEvNihXFUjGuwT4qgeIWFsiFcinin  
6Q42DwjRZBL1jcYpAYxP3WQFc+JvRlOaaWecklLmlLGBp9qjyzNzNhgT374T1YkgWJLTm4Jky7bW6HAMWJzT2vCpbSWLbtU=</SignatureValue>  
<KeyInfo>  
<X509Data>  
<X509Certificate>MIIGgjCCBGqgAwIBAgITFQAAAAVVlNqAr99awAAAAAAABTANBgkqhkiG9w0BAQsFADBJMRMwEQYKCZImiZ  
PyLGQBGRYDY29tMRUwEwYKCZImiZPyLGQBGRZjVsYWIxGzAZBgNVBAMTEkY1IExhYiBDQSAoMksxNlIyKTAeFw0xNjEyMTYxN  
TE1MjNaFw0xODEyMTYxNTE1MjNaMG8xCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJQQTEUMBIGA1UEBxMLRG93bmluZ3Rvd24xDzAN  
BgNVBAoTBkY1IExhYjEPMA0GA1UECxMGRjUgTGFiMRswGQYDVQQDExJmYWtlYWRmcy5mNWxhYi5jb20wgZ8wDQYJKoZIhvcNAQE  
BBQADgY0AMIGJAoGBALgtr7ROiet3GPUg0yWa2dGPoirQ9dI8XiA7BsUwjTUG5yhAysKm0ZsCstKN92a2e8HxoHxiEZL39XzTxg  
5+3fY4A8hWttlqkKoWutnUS3GpPhfoVdufr8bTcr/vhLPCkuy9GsiDqAMwuiX/B3r0EHqFk3utfL3KDxZ5V94ArwqJAgMBAAGjg  
gK/MIICuzAOBgNVHQ8BAf8EBAMCBaAwEwYDVR0AwwCgYIKwYBBQUHAwEweAYJKoZIhvcNAQkPBGswaTAOBggqhkiG9w0DAgIC  
AIAwDgYIKoZIhvcNAwQCAgCAMAsGCWCGSAFlAwQBKjALBglghkgBZQMEAS0wCwYJYIZIAWUDBAECMAsGCWCGSAFlAwQBBTAHBgU  
rDgMCBzAKBggqhkiG9w0DBzAdBgNVHQ4EFgQUL8AJuPouaekEIK8JuQAthnBS8wHwYDVR0jBBgwFoAUeyV8LWPBfaUCaLG/UR  
cYpOrjK48wgeMGA1UdHwSB2zCB2DCB1aCB0qCBz4aBzGxkYXA6Ly8vQ049RjUlMjBMYWIlMjBDQSUyMCEwMDI4MksxNlIyITAwM  
jksQ049V0lOMksxMlIyREMwMDEsQ049Q0RQLENOPVB1YmxpYyUyMEtleSUyMFNlcnZpY2VzLENOPVNlcnZpY2VzLENOPUNvbmZp  
Z3VyYXRpb24sREM9ZjVsYWrIsREM9Y29tP2NlcnRpZmljYXRl2b2NhdGlvbkxpc3Q/YmFzZT9vYmplY3RDbGFzcz1jUkxEaXN  
0cmlidXRpb25Qb2ludDCB0AYIKwYBBQUHAQEEgcMwgcAwgb0GCCsGAQxUFBzAChoGwbGRhcDovLy9DTj1GNSUyMExhYiUyMENBJT  
IwITAwMjgySzE2UjIhMDAyOSxDTj1BSUEsQ049UHVibGljJTIwS2V5JTIwU2VydmljZXMsQ049U2VydmljZXMsQ049Q29uZmlnd  
XJhdGlvbixEQz1mNWxhYixEQz1jb20/Y0FDZXJ0aWZpY2F0ZT9iYXNlP29iamVjdENsYXNzPWNlcnRpZmljYXRpb25BdXRob3Jp  
dHkwIQYJKwYBBAGCNxQCBBQeEgBXAGUAYgBTAGUAcgB2AGUAcjANBgkqhkiG9w0BAQsFAAOCAgEAdom2Hvlw9MTKZbr6Ic3MLDR  
I10QGnflAq9w0/t6H8HN1jnEW8RTikIEpp2nOK7GknFq2161mJ4l5cRGroCyWsHN8to6VqhxqnESYHRyxwZDpS6a8JO4AYc111G  
fRWpB4nOIqs86aboUJDU+vRzrJHeuI1NzsI502i7fjlYqQVtE3FO2VIbekqx9zjHnliAX6l+VbDMFX8P8lP40U9rAIzHUPF+j3p  
34i+4tPtv1/bwTco2EZE8hy2XvJ4xHXzpXYytchRhv+8glYNKK229vhML0micJfnCJQ3xaiJ2e08/GSVoBF9x4J+z4V+XS9aZSn  
P2+N3tZESVVBA8U4kk9u6syfmDc4+ryoIw5XGcBIyitaH7FbKbYyUHY0XuWPHx6FOWWnCe2kIA/Zfs9IDCP/z07egIJabLymLC  
vRhOMyd1s5lajnTFfoFaDd7LlL1ipz94OdhxJ5/Aga7sTEtFPbjnfcSZ8lFglQUOkaKuZt6D/LQ/TW6TyDqPC3RDCoaqkY4MXgnm  
P0dUk9ql1y2qFU2l692ZDZQPB4Tiaa3yXDKwDwCWITQ0YBvIiCcSWoMKXXea96Q2lB3R9n7v9Y6I7eSniZjGqlYYQ3Bdi3FMVz+  
HGPeMOFq6HbzgnNtjFKwjqokUbwpwA7vZOQmFwEahEEaCPTpK25h4LSpLPtYa3fjfQ=</X509Certificate>  
</X509Data>  
</KeyInfo>  
</Signature>  
</saml:Assertion>  
</t:RequestedSecurityToken>  
<t:TokenType>urn:oasis:names:tc:SAML:1.0:assertion</t:TokenType>  
<t:RequestType>http://schemas.xmlsoap.org/ws/2005/02/trust/Issue</t:RequestType>  
<t:KeyType>http://schemas.xmlsoap.org/ws/2005/05/identity/NoProofKey</t:KeyType>  
</t:RequestSecurityTokenResponse>


5. The results are injected into a form embedded in an HTTP::respond 200 content with javascript that will automatically POST the form data to SharePoint.  SharePoint accepts the assertion from its Trusted Identity Provider.

  

The current solution pulls attributed from LDAP, but this isn't required, you can basically enter anything as username and SharePoint accepts it without question.

iRule/TCL Portion

when HTTP_REQUEST {  
    #  Wctx: This is some session data that the application wants sent back to  
    #  it after the user authenticates.  

    set wctx [URI::decode [URI::query [HTTP::uri] wctx]]  

    #  Wa=signin1.0: This tells the ADFS server to invoke a login for the user.  

    set wa [URI::decode [URI::query [HTTP::uri] wa]]  

    #  Wtrealm: This tells ADFS what application I was trying to get to.  
    #  This has to match the identifier of one of the relying party trusts  
    #  listed in ADFS.  wtrealm is used in the Node.JS side, but we dont need it  
    #  here.    
    #  Kept getting errors from APM, this fixed it.  

    node 127.0.0.1  

    #  Make sure that the user has authenticated and APM has created a session.  

    if {[HTTP::cookie exists MRHSession]} {  

        #log local0. "Generate POST form and Autopost "  

        #  tmpresponse is the WS-Fed Assertion data, unencoded, so straight XML  

        set tmpresponse [ACCESS::session data get session.custom.idam.response]  

        #  This was the pain to figure out.  The assertion has to be POSTed to  
        #  SharePoint, this was the easiest way to solve that issue.  Set timeout  
        #  to half a second, but can be adjusted as needed.  

        set htmltop "<html><script type='text/javascript'>window.onload=function(){ window.setTimeout(document.wsFedAuth.submit.bind(document.wsFedAuth), 500);};</script><body>"  
        set htmlform "<form name='wsFedAuth' method=POST action='https://sharepoint.f5test.com/_trust/'><input type=hidden name=wa value=$wa><input type=hidden name=wresult value='$tmpresponse'><input type=hidden name=wctx value=$wctx><input type='submit' value='Continue'></form/>"  
        set htmlbottom "</body></html>"  
        set page "$htmltop $htmlform $htmlbottom"  

        HTTP::respond 200 content $page  
    }  
}  

when ACCESS_POLICY_AGENT_EVENT {  
    #  Create the ILX RPC Handler  

    set fakeadfs_handle [ILX::init fakeadfs_extension]  
    #  Payload is just the incoming Querystring  

    set payload [ACCESS::session data get session.server.landinguri]  

    #  Currently, the mapped attributes are Email & UPN.  In some environments,  
    #  this may match, for my use case, they will not, so there is an LDAP AAA  
    #  which is queried based on the logon name (email), and the UPN is retrieved  
    #  from LDAP.  
    set AttrUserName [ACCESS::session data get session.logon.last.username]  
    set AttrUserPrin [ACCESS::session data get session.ldap.last.attr.userPrincipalName ]  

    #  Current solution uses Node.JS SAML module and can support SAML11, as well  
    #  as SAML20.  The APM policy calls the irule even ADFS, with generates the token  
    #  based on the submitted QueryString and the logon attributed.  
    switch [ACCESS::policy agent_id] {  
              "ADFS" {  
                    log local0. "Received Process request for FakeADFS, $payload"  
                    set fakeadfs_response [ILX::call $fakeadfs_handle Generate-WSFedToken $payload $AttrUserName $AttrUserPrin]  
                    ACCESS::session data set session.custom.idam.response $fakeadfs_response  
              }  
    }  
}  

#  This may or may not be needed, they arent populated with actual values, but  
#  have not tested WITHOUT yet.  
#  
#  MSISAuth and MSISAuth1 are the encrypted cookies used to validate the SAML  
#  assertion produced for the client. These are what we call the "authentication  
#  cookies", and you will see these cookies ONLY when AD FS 2.0 is the IDP.  
#  Without these, the client will not experience SSO when AD FS 2.0 is the IDP.  
#  
#  MSISAuthenticated contains a base64-encoded timestamp value for when the client  
#  was authenticated. You will see this cookie set whether AD FS 2.0 is the IDP  
#  or not.  
#  
#  MSISSignout is used to keep track of the IDP and all RPs visited for the SSO  
#  session. This cookie is utilized when a WS-Federation sign-out is invoked.  
#  You can see the contents of this cookie using a base64 decoder.  
#  MSISLoopDetectionCookie is used by the AD FS 2.0 infinite loop detection  
#  mechanism to stop clients who have ended up in an infinite redirection loop  
#  to the Federation Server. For example, if an RP is having an issue where it  
#  cannot consume the SAML assertion from AD FS, the RP may continuously redirect  
#  the client to the AD FS 2.0 server. When the redirect loop hits a certain  
#  threshold, AD FS 2.0 uses this cookie to detect that threshold being met,  
#  and will throw an exception which lands the user on the AD FS 2.0 error page  
#  rather than leaving them in the loop. The cookie data is a timestamp that is  
#  base64 encoded.  

when ACCESS_ACL_ALLOWED {  
    HTTP::cookie insert name "MSISAuth" value "ABCD" path "/adfs"  
    HTTP::cookie insert name "MSISSignOut" value "ABCD" path "/adfs"  
    HTTP::cookie insert name "MSISAuthenticated" value "ABCD" path "/adfs"  
    HTTP::cookie insert name "MSISLoopDetectionCookie" value "ABCD" path "/adfs"  
}  

iRulesLX / Node.JS Portion

If you you are starting from scratch, the npm install xxx --save option below is required.  If you are importing the workspace or the github solution, npm update will pull in the latest version of all the referenced modules.

/* Import the f5-nodejs module. */  

var f5 = require('f5-nodejs');  

/* Import the additional Node.JS Modules 
  npm install saml 
  npm install querystring 
  npm install fs 
  npm install moment 
  npm install https 
*/  

var saml11 = require('saml').Saml11;  
var queryString = require('querystring');  
var fs = require('fs');  
var moment = require('moment');  
var https = require('https');  

/*  timeout is the length of time for the assertion validity 
    wsfedIssuer is, believe it or not, the Issuer 
    SigningCert, SigningKey are the required certificate and key pair 
    for signing the assertion and specifically the DigestValue. 
*/  

var timeout = 3600;  
var wsfedIssuer = "http://fakeadfs.f5test.com/adfs/services/trust";  
var SigningCert = "/fakeadfs.f5test.com.crt";  
var SigningKey = "/fakeadfs.f5test.com.key";  

/* Create a new rpc server for listening to TCL iRule calls. */  
var ilx = new f5.ILXServer();  
  
ilx.addMethod('Generate-WSFedToken', function(req,res) {  

    /* Extract the ILX parameters to add to the Assertion data 
      req.params()[0] is the first passed argument 
      req.params()[1] is the second passed argument, and so on. 
    */  

    var query = queryString.unescape(req.params()[0]);  
    var queryOptions = queryString.parse(query);  
    var AttrUserName = req.params()[1];  
    var AttrUserPrincipal = req.params()[2];  
    var wa = queryOptions.wa;  
    var wtrealm = queryOptions.wtrealm;  
    var wctx = queryOptions.wctx;  

    /* This is where the WS-Fed gibberish is assembled.  Moment is required to 
      insert the properly formatted time stamps.*/  

    var now = moment.utc();  
    var wsfed_wrapper_head = "<t:RequestSecurityTokenResponse xmlns:t=\"http://schemas.xmlsoap.org/ws/2005/02/trust\">";  
    wsfed_wrapper_head += "<t:Lifetime><wsu:Created xmlns:wsu=\"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd\">" + now.format('YYYY-MM-DDTHH:mm:ss.SSS[Z]') +"</wsu:Created>";  
    wsfed_wrapper_head += "<wsu:Expires xmlns:wsu=\"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd\">" + now.add(timeout, 'seconds').format('YYYY-MM-DDTHH:mm:ss.SSS[Z]') + "</wsu:Expires>";  
    wsfed_wrapper_head += "</t:Lifetime><wsp:AppliesTo xmlns:wsp=\"http://schemas.xmlsoap.org/ws/2004/09/policy\"><wsa:EndpointReference xmlns:wsa=\"http://www.w3.org/2005/08/addressing\">";  
    wsfed_wrapper_head += "<wsa:Address>" + wtrealm + "</wsa:Address>";  
    wsfed_wrapper_head += "</wsa:EndpointReference></wsp:AppliesTo><t:RequestedSecurityToken>";  

    /* Generate and insert the SAML11 Assertion.  These attributed are 
      configured previously in the code. 

      cert: this is the cert used for encryption 
      key: this is the key used for the cert 
      issuer: the assertion issuer 
      lifetimeInSeconds: timeout 
      audiences: this is the application ID for sharepoint, urn:sharepoint:webapp 
      attributes:  these should map to the mappings created for the IDP in SharePoint 
      */  

    var saml11_options = {  
        cert: fs.readFileSync(__dirname + SigningCert),  
        key: fs.readFileSync(__dirname + SigningKey),  
        issuer: wsfedIssuer,  
        lifetimeInSeconds: timeout,  
        audiences: wtrealm,  
        attributes: {  

            'http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress':  AttrUserName  ,  
            'http://schemas.xmlsoap.org/ws/2005/05/identity/claims/upn': AttrUserPrincipal  
        }  
    };  

    /* Sign the Assertion */  

    var signedAssertion = saml11.create(saml11_options);  

    /* Add the WS-Fed footer */  

    var wsfed_wrapper_foot = "</t:RequestedSecurityToken><t:TokenType>urn:oasis:names:tc:SAML:1.0:assertion</t:TokenType><t:RequestType>http://schemas.xmlsoap.org/ws/2005/02/trust/Issue</t:RequestType><t:KeyType>http://schemas.xmlsoap.org/ws/2005/05/identity/NoProofKey</t:KeyType></t:RequestSecurityTokenResponse>";  

    /* Put them all together */  

    var wresult = wsfed_wrapper_head + signedAssertion + wsfed_wrapper_foot;  

    /* respond back to TCL with the complete assertion */  

    res.reply(wresult);  

});  
  
/* Start listening for ILX::call and ILX::notify events. */  

ilx.listen();

And thats it.  You can now get away with WS-Fed and SAML1.1 assertions with a quick import to your BIG-IP.

Published Jan 05, 2017
Version 1.0

Was this article helpful?

17 Comments