Time again to attempt to implement that exciting technology, Federation Services (Web Single Sign On, SAML, WS-Federation, or whatever you want to call it) with SharePoint. Last time we tried this, SharePoint 2010 was a baby product, MS was just testing the waters with SAML 2.0 support in ADFS 2.0, and Shibboleth 2 was pretty new stuff here at UVM. The whole experience was unsatisfying. SharePoint STS configuration was full of arcane PowerShell commands, ADFS setup was complicated by poor farm setup documentation, and interop of Shibboleth 2 with ADFS 2 was not documented at all. After wading though all of that mess, we ended up with user names being displayed as “i:05.t|adfsServiceName|userPrincipalName” (bleagh!), and with many applications that could not deal with Web SSO authenticators. My general conclusion was that SharePoint really was not ready for federated login.
Four years later, things have changed a bit. STS configuration still requires dense PowerShell commands, but at least it is better documented. ADFS and Shibboleth interoperability also are excellently documented at this point. Microsoft has most Office apps working with passive SAML authenticators, and has pledged to get the rest working this year. While I would not judge the use of ADFS (with or without Shibboleth) to be the easy route to take to SharePoint 2013 deployment, it at least looks functional at this point. So let’s kick the tires and see how it works…
Part 1: ADFS Setup
First, we need to setup ADFS. We chose to deploy “ADFS 3” using Windows Server 2012 R2 as the OS platform. ADFS 3 is required to support the new “workplace join” feature of Server 2012 R2. Since we want to test this, we would need ADFS 3 anyway. Unfortunately, ADFS on Server 2012 R2 is pretty virgin territory, and does not have the same troubleshooting resources available for it as do earlier releases.
Most of the configuration steps followed the TechNet documentation without variation. We did find that we needed to modify the ACL on the service account used to run ADFS… we added the “Authenticated Users” principal to the ACL, and assigned the “Read all properties” right.
For us, the most complicating factor in ADFS 3 deployment is the replacement of IIS with the Windows Kernel-mode HTTP server “http.sys”. When we started experiencing connectivity problems with various clients to ADFS, we had no experience with HTTP.sys to assist in troubleshooting. Most articles on HTTP.sys relate to remote desktop services, and with Server 2003. Our problems with HTTP.sys were rooted in an undocumented requirement for clients to submit SNI (Service Name Indicator) information in their TLS “CLIENT HELLO” sequence. I had to open a support case with Microsoft to resolve this problem, and only afterword was I able to find any Internet discussions that reflected MS advice:
http://social.msdn.microsoft.com/Forums/vstudio/en-US/d514b5a0-c01c-4ce4-b589-bca890e5921d/how-to-properly-setup-lb-probe-for-adfs-30-servers?forum=Geneva
It seems that if http.sys is bound using the hostname:port format, then TLS
will require SNI. If the binding is instead specified using ipAddr:port, SNI
will not be required. To fix our problem, we just needed to add a second HTTPS
certificate binding using an IP address. In this case, we just used
“0.0.0.0:443”. Here is the procedure:
- On each ADFS server and proxy, open an elevated command prompt
- run: netsh http show sslcert
- Record the certificate hash and application ID for the certificate used by
ADFS - run: netsh http add sslcert ipport=0.0.0.0:443 certhash= appid={}
Part 2: Configuring SharePoint to use ADFS
I started with Microsoft’s guide to configuring SAML authentication for
SharePoint 2013 using ADFS:
http://technet.microsoft.com/en-us/library/hh305235(v=office.15).aspx
This
is a well written guide, and fairly easy to follow. The only issue I take with
the article is the recommendation to use the “emailAddress” claim as the
“identifier claim” in SharePoint. In many federated login scenarios, a foreign
Idp may not want to release the email address attribute. However, some
variation of UPN likely will be released. In the case of the InCommon
federation, the “ePPN” value (eduPersonPrincipalName) generally is available to
federation partners. For this reason I chose to implement “UPN” as the
“identifier claim” in the last command of phase 3 of the document:
$ap = New-SPTrustedIdentityTokenIssuer -Name -Description -realm $realm -ImportTrustCertificate $cert -ClaimsMappings $emailClaimMap,$upnClaimMap,$roleClaimMap,$sidClaimMap -SignInUrl $signInURL -IdentifierClaim $roleClaimmap.InputClaimType
Part 3: Migrating SharePoint Users From Windows to Claims
Since we are planning an upgrade, not a new deployment, we need migrate existing Windows account references in SharePoint to federated account references. To make this happen, we need to establish the federated account identity format. I simply log in to an open-access SharePoint site as an ADFS user, and record the account information. In this case, out accounts look like this:
i:0e.t|adfs.uvm.edu|user@campus.ad.uvm.edu
and groups:
c:0-.t|adfs.uvm.edu|samAccountName
We then can use PowerShell to find all account entries in SharePoint, and use the “Move-SPUser” PowerShell cmdlet to convert them. I am still working on a final migration script for the production cutover, and I will try to post it here when it is ready.
Of some concern is keeping AD group permissions functional. It turns out that SharePoint will respect AD group permissions for ADFS principals, but there are a few requirements:
- The incoming login token needs to contain the claim type “http://schemas.microsoft.com/ws/2008/06/identity/claims/role“,
and
that this claim type needs to contain the SamAccountID (or CN) of the
AD
Group that was granted access to a site. (In ADFS, this means that you
need to release the AD LDAP Attribute “Token-Groups – Unqualified Names” as the
outgoing claim type “Role”. - When adding AD Groups as permissions in SharePoint, we need to use the
“samAccountName” LDAP attribute as the identifier claim. The LDAPCP (see Part
4) utility makes this easy as it will do this for us automatically when
configured to search AD.
Requirement 1 could be a problem when using Shibboleth as the authentication
provider. Our Shibboleth deployment does not authenticate against AD, so a
Shibboleth ticket will not contain AD LDAP “tokenGroup” data in the “role”
claim. I am working with the Shibboleth guys to see if there is any way
to augment Shibboleth tokens with data pulled from AD.
Part 4: The SharePoint PeoplePicker and ADFS
Experienced SharePoint users all know (and mostly love) the “people picker”
that searches Active Directory to validate user and group names that are to be
added to the access list for a SharePoint site. One of the core problems with
federation services is that they are authentication systems only. ADFS
and Shibboleth do not implement a directory service. You cannot do a lookup on
an ADFS principal that a user adds to a SharePoint site. This is particularly
irksome, since all of our ADFS users actually have matching accounts in Active
Directory.
Fortunately, there is a solution… you can add a “Custom Claims Provider” into
SharePoint which will augment incoming ADFS claims with matching user data
pulled from Active Directory. This provider also integrates with the
PeoplePicker to allow querying of AD to validate Claims users that are being
added to a SharePoint site. A good write-up can be found here:
http://blogs.msdn.com/b/kaevans/archive/2013/05/26/fixing-people-picker-for-saml-claims-users-using-ldap.aspx
“But I don’t want to compile a SharePoint solution using Visual Studio,” I
hear you (and me) whine. No problem… there is a very good pre-build solution
available from CodePlex:
http://ldapcp.codeplex.com/discussions/361947
Normally I do not like using third-part add-ons in SharePoint. I will make
an exception for LDAPCP because:
- It works.
- It saves me hours of Visual Studio work.
- It is a very popular project and thus likely to survive on CodePlex until it
is no longer needed. - If the project dies, we can implement our own Claims Provider using
templates provided elsewhere with (hopefully) minimal fuss.
My only outstanding problem with LDAPCP is that it will not query principals
in our Guest AD forest. However, there are some suggestions from the developer
along these lines:
http://ldapcp.codeplex.com/discussions/478092
To summarize, the developer recommends compiling our own version of LDAPCP
from the provided source code. We would use the method “SetLDAPConnections”
found in the “LDAPCP_Custom.cs” source file to add an additional LDAP query
source to the solution. I will try this as time permits.
Part 5: Transforming Shibboleth tokens to ADFS
So far we have not strayed too far from well-trodden paths on the Internet.
Now we get to the fun part… configuring ADFS as a relying party to our
Shibboleth Idp, then transforming the incoming Shibboleth SAML token into an
ADFS token that can be consumed by SharePoint.
Microsoft published a rather useful guide on ADFS/Shibboleth/InCommon
integration:
http://technet.microsoft.com/en-us/library/gg317734(WS.10).aspx
Using
this guide we were able to set up ADFS as a relying party to our existing
Shibboleth Idp with minimal fuss. Since we already have an Idp, we skipped most
of Step 1, and then jumped to Step 4 as we did not need to configure Shibboleth
as an SP to ADFS.
I had the local “Shibboleth Guy” add our ADFS server to the relying parties
configuration file on the Shib server, and release “uvm-common” attributes for
this provider. This allows SharePoint/ADFS users to get their
“eduPersonPrincipalName” (ePPN) released to ADFS/SharePoint from Shibboleth.
However, SharePoint (and ADFS) do not natively understand this attribute, so we
configure a “Claim Rule” on the “Claims Provider Trust” with Shibboleth. The
rule is an “Acceptance Transformation Rule” that we title “Transform ePPN to
UPN”, and it has the following syntax:
c:[Type ==
"urn:oid:1.3.6.1.4.1.5923.1.1.1.6"]
=> issue(Type =
"http://schemas.xmlsoap.org/ws/2005/05/identity/claims/upn",
Issuer =
c.Issuer, OriginalIssuer = c.OriginalIssuer, Value = c.Value,
ValueType =
c.ValueType);
The “urn:oid:1.3.6.1.4.1.5923.1.1.1.6” bit is the
SAML 2 identifier for the ePPN type.
After configuring a basic Relying Party trust with SharePoint 2013, I need to
configure Claims Rules that will release Shibboleth User attributes/claims to
SharePoint. You could use a simple “passthough” rule for this. However, I want
incoming Shibboleth tokens that have a “@uvm.edu” UPN suffix to be treated as
though they are Active Directory users. To accomplish this, I need to do a
claims transformation. In AD, the user UPN has the “@campus.ad.uvm.edu” suffix,
so let’s transform the Shibboleth UPN using a Claim Rule on the SharePoint
Relying Party Trust:
c:[Type == "urn:oid:1.3.6.1.4.1.5923.1.1.1.6",
Value =~ "@uvm\.edu$"]
=> issue(Type =
"http://schemas.xmlsoap.org/ws/2005/05/identity/claims/upn", Issuer = "AD
AUTHORITY", OriginalIssuer = c.OriginalIssuer, Value = regexreplace(c.Value,
"^(?[^@]+)@(.+)$", "${user}@campus.ad.uvm.edu"), ValueType =
c.ValueType);
NOTE: Code munged by WordPress. Contact me if you need exact syntax!
This appears to work… the first RegEx “@uvm\.edu$” should match an incoming
UPN that ends with “@uvm.edu”. In the second set of regExps, we create a capture
group for the user portion of the UPN (which is everything from the start of the
value up to (but not including) the “@” character), and place the captured data
into a variable called “user”. We then replace everything trailing the user
portion with “@campus.ad.uvm.edu”.
However, as noted above, the incoming Shibboleth SAML token does not contain
AD group data in the “Role” attribute, so users authenticating from Shibboleth
cannot get access to sites where they have been granted access using AD groups.
Not good! Fortunately, there is a solution.
An only-hinted at, and certainly not well documented capability of the Claim
Rule Language is the ability to create an issuable token with claims originating
from more than one identity store. In our case, we need to supplement the
incoming Shibboleth SAML token with “roles” claims obtained from Active
Directory. I do this during the release of the ADFS token to the SharePoint
relying party, using the following rule:
c:[Type ==
"http://schemas.xmlsoap.org/ws/2005/05/identity/claims/upn", Value =~
"^.+@campus.ad.uvm.edu$"]
=> issue(store = "Active Directory", types =
("http://schemas.microsoft.com/ws/2008/06/identity/claims/role"), query =
";tokenGroups;CAMPUS\{0}", param = regexreplace(c.Value,
"^(?.+)@campus.ad.uvm.edu$", "${user}"));
NOTE: code munged by WordPress. Contact me if you need exact syntax!
The issuance part of this rule bears some discussion. We use the “query”
string to collect tokenGroups (or recursive group memberships). The query is run
against the Active Directory ADFS attribute store (hence store = “Active
Directory”). The query must take the format:
QUERY =
[QUERY_FILTER];[ATTRIBUTES];[DOMAIN_NAME]\[USERNAME]
In our case, there is no
filter specified. According to TechNet, the default filter is
“samAccountName={0}”, which really is what we want. We want to query a
particular samAccountName for its tokenGroups. The DOMAIN\Username portion needs
to be present, but the username portion is ignored. We could have used
“jimmyJoeJimBob” instead of “{0}”.
The {0} represents the value collected in the “param” section of the issuance
rule. I used a regular expression replacement rule to strip out the
samAccountName from the UPN. Of course, I could simply have used the UPN in the
query filter, which would have been cleaner. Maybe I will update this for a
future project.
I had to solicit help from the Windows Higher Education list to figure this
one out. There is limited documentation on the “query” portion of this rule
available here. It is the only documentation I can find, and it does document
the bizarre LDAP/AD query syntax discussed above:
http://technet.microsoft.com/en-us/library/adfs2-help-attribute-stores%28WS.10%29.aspx
…And some more general docs on the Claim Rule Language here:
http://social.technet.microsoft.com/wiki/contents/articles/4792.understanding-claim-rule-language-in-ad-fs-2-0-higher.aspx?CommentPosted=true#commentmessage
…And an interesting use-case breakdown of the Language here:
https://jorgequestforknowledge.wordpress.com/category/active-directory-federation-services-adfs/claims-rule-language/
…And a video from Microsoft’s “Identity Architects” here:
http://www.youtube.com/watch?v=G279c_5tHfs
Part 6: Where are you from? Notes on Home Realm Discovery
When an ADFS server has multiple “Claims Provider Trusts” defined, the ADFS
login page automatically will create a “WAYF”, or “Where are you from?” page to
allow the user to select from multiple authentication providers. In our case,
the user would see “Active Directory” and “UVM Shibboleth”. Since I would not
want to confuse people with unnecessary choices, we can disable the display of
one of these choices using PowerShell:
Set-AdfsRelyingPartyTrust -TargetName "SharePoint 2013" -ClaimsProviderName "UVM Shibboleth"
In this sample, “SharePoint 2013” is the name of the relying party defined in ADFS for which you want to set WAYF options. “UVM Shibboleth” is the Claims Provider Trust that you want used. This value can be provided as an array, but in this case we are going to provide only one value… the one authenticator that we want to use. After configuring this change, ADFS logins coming from SharePoint get sent straight to Shibboleth for authentication.
Part 7: The Exciting Results
Only a sysadmin could call this exciting…
Given how heavily MS invested in implementing WS-Federation and WS-Trust into their products (MS Office support for federation services was, to the best of my knowledge, focused entirely on the WS-* protocols implemented in ADFS 1.0), I was not expecting any client beyond a web browser to work with Shibboleth. Imagine my surprise…
Browsers:
IE 11 and Chrome both login using Shib with no problems. Firefox works, but not without a glitch… upon being redirected back to SharePoint from our webauth page, we get a page full of un-interpreted html code. Pressing “f5” to refresh clears the problem.
Office 2013 Clients:
All core Office 2013 applications appear to support opening of SharePoint documents from links in the browser. Interestingly, it appears that Office is able to share ADFS tokens obtained by Internet Explorer, and vice versa. The ADFS token outlives the browser session, too, so you actually have to log off of ADFS prevent token re-use. I tested the “Export to Excel” and “Add to Outlook” options in the SharePoint ribbon, and both worked without a fuss.
Getting Office apps to open content in SharePoint directly also works, although its a bit buggy. Sometimes our webauth login dialog does not clear cleanly after authentication.
SkyDrive Pro (the desktop version included with Office 2013) (soon to be “OneDrive for Business) also works with Shibboleth login, amazingly. The app-store version does not work with on-premises solutions at all, so I could not test it.
Mobile Clients:
I was able to access a OneNote Notebook that I stored in SharePoint using OneNote for Android. However, it was not easy. OneNote for Android does not have a dialog that allow for the adding of notebooks from arbitrary network locations. I first had to add the notebook from a copy of OneNote 2013 for Windows that was linked to my Microsoft account. The MS account then recorded the existence of the notebook. When I logged in to OneNote on the Android, it picked up on the SharePoint-backed notebook and I was able to connect.
The OneNote “metro” app does not appear to have the same capability as the Android app. I cannot get it to connect to anything other than Office 365 or CIFS-backed files.
I was unable to test Office for iOS or Android because I do not own a device on which those apps are supported.
I still need to look at the “Office Document Connection” that comes with Office for the Mac, and at WebDAV clients, and perhaps some other third-party SharePoint apps to see if they work.