The post
below was some findings from a project related to the authentication between the
front and back end services, and F5 configurations. It closely matches issues identified in my previous post for the SSO server but that was using ntlm/kerberos (I have a feeling
that we didn't set up the SPNs for kerberos correctly). Ultimately I bet that a similar configuration
to that below but set up for one-shot-kerberos and using the SPN in the Identity
column would have resolved this issue.
tldr;
Server
config:
Configure
the server to accept client certificates using PeerTrust - allows us to
authorise the client
Define
the server certificate (the identity of the service - important later on) -
allows message security to work
<behavior name="certificate">
<serviceMetadata httpGetEnabled="true" httpsGetEnabled="true" />
<serviceDebug includeExceptionDetailInFaults="true" />
<serviceCredentials>
<clientCertificate>
<authentication certificateValidationMode="PeerTrust" />
</clientCertificate>
<serviceCertificate findValue="<blah>" storeLocation="LocalMachine" x509FindType="FindBySubjectName"
/>
</serviceCredentials>
</behavior>
configure
the wsHttpBindings to use Message security,
items
highlighted yellow - this is used for the message security and allows load
balancing without sticky sessions
items
highlighted green - this is used to authenticate the client using a
certificate
<wsHttpBinding>
<binding name="certauth" maxReceivedMessageSize="2147483647">
<readerQuotas maxStringContentLength="2147483647" maxArrayLength="2147483647" />
<security mode="Message">
<message algorithmSuite="TripleDesSha256" establishSecurityContext="false" negotiateServiceCredential="false" clientCredentialType="Certificate" />
</security>
</binding>
</wsHttpBinding>
Configure
the endpoint, basically accept request on <endpoint>/certauth
and applies the appropriate message security and authentication
<endpoint name="certauth" address="certauth" binding="wsHttpBinding" bindingConfiguration="certauth" contract="<contract>" />
Client
Config:
Configure
the client to accept the server's security configuration using PeerTrust
Configure
the certificate that this client will pass as its credentials
<endpointBehaviors>
<behavior name="certificate">
<clientCredentials>
<clientCertificate findValue="<name>" storeLocation="LocalMachine" x509FindType="FindBySubjectName"
/>
<serviceCertificate>
<authentication certificateValidationMode="PeerTrust" />
</serviceCertificate>
</clientCredentials>
</behavior>
</endpointBehaviors>
Configure
the security bindings, must match the service bindings
<wsHttpBinding>
<binding name="certauth" maxReceivedMessageSize="2147483647">
<readerQuotas maxStringContentLength="2147483647" maxArrayLength="2147483647" />
<security mode="Message">
<message algorithmSuite="TripleDesSha256" establishSecurityContext="false" negotiateServiceCredential="false" clientCredentialType="Certificate" />
</security>
</binding>
</wsHttpBinding>
Configure
the endpoint. Note the /certauth suffix.
the
identity element is essential, it needs to match the serviceCredentials defined
on the service.
<endpoint address="<endpoint>/certauth" binding="wsHttpBinding" bindingConfiguration="certauth" contract="<contract>" behaviorConfiguration="certificate">
<identity>
<certificateReference findValue="<name>" x509FindType="FindBySubjectName" storeLocation="LocalMachine" storeName="TrustedPeople"/>
</identity>
</endpoint>
long version;
To do
this we need to use message security for the following reasons
- We are using SSL offload, so transport security is not supported (works over SSL only)
- We are using certificate auth, which is not allowed on TransportWithCredentialOnly
- We are working across a domain boundary so we cannot use windows authentication
The
second complexity is that we are working in a load balanced environment.
This adds complexity as WCF negotiates a security context to generate the
message security before sending the message. If the Load balancer sends
the message to a different server to the one the security context was
generated on, then this will cause intermittent security failures.
The two
general solutions to this are to;
- enable sticky sessions which the F5 then "guarantees" that the security context and message occur on the same server.
- This did not seem to work - with sticky sessions enabled with the following config (), we still received this errors.
- If the security context lasted longer than the sticky session timeout, then this could still cause problems, but I don't think this was the case.
- I have a feeling this was caused by not setting the 'identity' correctly in the WCF config which was a key part of the final working solution, but it should still have worked regardless as both front end servers had success connecting to both back end servers.
- set wcf establishSecurityContext="false" which basically sets up security for every message, rather than establishing a long term security context, each message would have its own security context applied.
- this did not work. I have encountered this before where this was the supposed fix and did not actually resolve the issue, but I thought that it was related to ntlm issues at the time.
- Sticky sessions and this fix should pretty much have guaranteed the fix as you would never have the sticky session timeout before the security context expired (because it is only ever generated right before the message is sent).
- This did not work. I have no explanation for this.
A final
solution that I had previously read which allows a configuration to work
without sticky sessions is to set
negotiateServiceCredentials="false", however all examples point this
to enable "kerberos-one-shot" which allows message security
using kerberos if the services are configured with domain users and SPNs.
As we work across domains this wasn't acceptable.
I
was finally able to put a few things together and determined that the
use of "establishSecurityContext" along with
"negotiateServiceCredentials" should be possible without
kerberos if you use certificate security (separate from certificate
authentication). This had partially been done with the
"servicebehaviour" element which defined the serviceCredentials on
the server using certificates, and peerTrust for serviceCertificate on the
client. However the next step was to define the serviceIdentity on the
client config so that the client knew which certificate to encrypt the message
with ( public key) instead of negotiating with the service to
determine its certificate before encrypting.
Setting
the Endpoint Identity and setting establishSecurityContext="false"
and negotiateServiceCredentials="false" meant that the client now had
enough info to encrypt the message without asking the service anything, and the
service could decrypt it.
Note
that NONE of these issue related to the authentication
certificate not being accepted, it was the "message security"
that was the problem, it just happened to require message security so that it
could attach the authentication to the payload.
A side
effect of this is that we should now be able to get rid of sticky sessions.