Exploring WCF Web Services
LANGUAGES: C#
TECHNOLOGIES: .NET 3.5 | WCF
Securing Workflow Services
Exploring Ways You Can (and Can t) Secure Workflow Services
By Michele Leroux Bustamante
This month we ll explore how to secure calls to a Workflow Service and call from a Workflow Service to downstream WCF services, as well as other related issues. If you have questions about migrating your existing ASMX or WSE Web services to WCF, or questions regarding WCF as it relates to Web services, please send them to [email protected]!
Last month I provided an overview of Workflow Services, a feature of .NET Framework 3.5 that improves communication between WCF and Windows Workflow Foundation (WF, or Workflow; see Workflow Services). I explained how you can expose a workflow as a WCF service and how you can call WCF services from a workflow. In the process you also learned how to configure two activities: ReceiveActivity and SendActivity. ReceiveActivity helps you implement a WCF service operation as part of a workflow, and SendActivity helps you to call a specific WCF service operation.
I left the discussion of securing Workflow Services for this month so I could spend time on the details. If you care about security and most of us do you ll want to know how you can secure calls into the Workflow Service through each ReceiveActivity, as well as how you can make secure calls to WCF services from a SendActivity. The remainder of this article will summarize what you can do, what the limitations are, and what workarounds are available.
ReceiveActivity Security Features
Workflow Services are typically initialized through a call to a ReceiveActivity which is an implementation of a WCF service operation. Calls that execute a ReceiveActivity can be secured in the following manner:
- WCF configuration settings for the ReceiveActivity endpoint control the client credential type, authentication behavior, and authorization behavior.
- Role-based permission demands can be set through ReceiveActivity properties.
- Custom authorization code can be provided through ReceiveActivity properties.
Figure 1 illustrates the execution order of these security features. The WCF pipeline authenticates and authorizes credentials according to the associated service behavior. Before initializing the workflow instance, role-based permission settings for the ReceiveActivity are validated using traditional .NET Framework role-based permission demands. After the workflow is initialized, any custom code supplied to validate the caller is executed prior to executing the ReceiveActivity sequence.
Figure 1: ReceiveActivity and
related security checkpoints.
Note: The workflow instance beginning with the ReceiveActivity sequence will be executed on a new thread, distinct from the initial WCF thread allocated to the request. This will become important when I discuss identities.
In the next sections I ll discuss each of these security features in more detail.
Authenticating and Authorizing Client Credentials
A ReceiveActivity is associated with a particular service contract and operation. The host process for a Workflow Service loads a service model configuration for the service type implementing that contract (so clients can send messages that will reach the implementing ReceiveActivity). In that sense, standard WCF configuration settings are used to secure calls to a Workflow Service endpoint.
Figure 2 shows an example of a WCF configuration that defines a Workflow Service with a single endpoint. The settings require a UserName credential, authentication using a custom password validator, and authorization using a custom authorization policy.
behaviorConfiguration="serviceBehaviorUserName"> binding="wsHttpContextBinding"
bindingConfiguration="wsHttpUserName" contract="IWFService">
customUserNamePasswordValidatorType="SequentialWFService.CustomPasswordValidator, SequentialWFService"/> x509FindType="FindBySubjectName"/> Figure 2: Sample
service model configuration for a Workflow Service. Note: Obviously
security features of WCF are quite extensive. I assume you have some
familiarity with WCF security to follow along here. If not, see the Additional Resources sidebar. A context-aware binding is required for Workflow Services,
which means the service endpoint must use either BasicHttpContextBinding,
WSHttpContextBinding, or NetTcpContextBinding. Collectively, these bindings
support a number of different credential types that can be sent using transport
headers (transport security) or in SOAP message headers (message security). Figure
3 lists the supported credential types for each of the context-aware bindings. Binding Transport
Credentials Message
Credentials BasicHttpContextBinding None, Basic, Digest, NTML, Windows, Certificate UserName, Certificate WSHttpContextBinding None, Basic, Digest, NTML, Windows, Certificate None, Windows, UserName, Certificate, IssuedToken NetTcpContextBinding None, Windows, Certificate None, Windows, UserName, Certificate, IssuedToken Figure 3:
Credential support for context-aware bindings. As Figure 2 suggests, the binding configuration indicates
the credential type for the service. The selected client credential influences
the choice of authentication and authorization behaviors configured for the
service. The service behavior in Figure 2 includes a Every credential is associated with a default
authentication mechanism that can be customized in the associated service
behavior configuration. Authorization behaviors also can be customized for the
expected credential type. Figure 4 lists some typical credential types with a
summary of authentication and authorization settings that might be useful for
workflow services. Credential Type Authentication Authorization Windows Authenticate against the Windows domain. Gather roles from the Windows domain. UserName Authenticate against ASP.NET database using the ASP.NET
membership provider or, against a custom database using a custom password
validator. Gather roles from the ASP.NET database using the ASP.NET
role provider. Or, gather roles and other claims from a custom database using
a custom authorization policy. Certificate Authenticate against the certificate store. Specify roles or additional claims using a custom
authorization policy. IssuedToken Authenticate the signing certificate for the token. This
is not always possible if the certificate is not known in advance, as in
tokens issued via CardSpace personal cards. Specify roles or additional claims using a custom
authorization policy. Figure 4: Typical
credential types and associated authentication and authorization behaviors. WCF provides an authentication behavior for each
credential type that can be customized to meet application requirements. For
example, the UserNameAuthenticationBehavior configured in Figure 2 does not use
the default Windows domain authentication, but instead uses a custom password
validator. Each credential type has different authentication options
appropriate to the type. Authorization behaviors for the service also can be
customized. By default, the Windows domain is used to authorize requests
which means that Windows roles are gathered for the supplied credential. The
ServiceAuthorizationBehavior in Figure 2 specifies a custom authorization
policy to be responsible for evaluating the identity of the caller and
gathering roles or other relevant claims. In addition, this is where service
credentials are supplied (as needed) to secure communications. The point here is not to dissect every possible scenario
as there are many but to provide insight into how WCF handles authentication
and authorization before we discuss how Workflow Services are affected. As Figure
4 suggests, authentication behaviors should be configured according to the
credential type and application requirements just like any other WCF service.
Authorization behaviors for a Workflow Service should ensure these two things: I ll discuss identities and authorization policies in the
next few sections. If you re familiar with .NET Framework security features,
you know that every process runs under a specific Windows identity, and that
every thread has a security principal (an IPrincipal object) that can be
accessed via Thread.CurrentPrincipal. The process identity governs the Windows
resources to which your code has access, and is often used in the
authentication and authorization of calls made across processes. The security
principal is employed in .NET Framework role-based security (as in permission
demands). Figure 5 illustrates how Workflow Services allocates these
identities for incoming calls to a ReceiveActivity. When WCF authenticates the
request, an identity is created (an IIdentity object) for the credential.
Usually this identity becomes part of the security principal attached to the
thread via the installed authorization policy. In addition, any claims
associated with the identity are added to the security context (accessed
through the AuthorizationContext property of the ServiceSecurityContext).
Role-based security for Workflow Services depends on the
Thread.CurrentPrincipal to be set for the WCF thread. When the Workflow thread is created, the security
principal from the WCF thread is no longer accessible. Presumably,
authentication has already taken place. Authorization may have already been
completed through role-based permission demands. If not, to perform additional
authorization the Workflow thread relies on the security context to include the
necessary claim sets that describe the authenticated caller. The Workflow
thread has an empty security principal (a GenericPrincipal type), making
traditional role-based permission demands useless from the executing Workflow
thread. This is important. The only way to pass meaningful
information about the authenticated caller to the Workflow thread is to supply
the security context of the WCF thread with claims that will be useful to the
Workflow thread. In the case of Windows, Certificate, and IssuedToken
credentials, this is automatically handled in that Windows groups, Certificate
claims, and IssuedToken (usually SAML token) claims are provided in a ClaimSet.
If you want to add additional claims based on the authenticated caller s
identity, a custom authorization policy must be provided, as suggested in Figure
4. For the scenario in Figure 2, the
UserNameAuthorizationPolicy adds roles for the authenticated users into a new
ClaimSet, as shown in the IAuthorizationPolicy implementation in Figure 6. To
simplify things, this example shows you how to hard-code the roles for the
caller (obviously you d be looking up the roles based on the user name in a
real implementation). public class UserNameAuthorizationPolicy: IAuthorizationPolicy { public bool
Evaluate(EvaluationContext evaluationContext, ref
object state) { List as List IIdentity identity =
identities[0]; evaluationContext.Properties["Principal"] = new GenericPrincipal(identity, new string[]
{"Administrators", "Users"}); evaluationContext.AddClaimSet(this,new DefaultClaimSet(new
Claim("Role", "Administrators",
Rights.PossessProperty), new Claim("Role", "Users",
Rights.PossessProperty))); return true; } // remaining
implementation } Figure 6: A sample
custom authorization policy for UserName credentials. This ClaimSet then appears in the claim sets available to
the Workflow thread. The obvious limitation here is that the Workflow thread
does not have access to the original security token, and thus cannot
impersonate it or otherwise use it for calls to downstream WCF services. I
wouldn t generally recommend impersonation for downstream service calls, so
this is not an issue. As for access to the original security token, this would
be an issue in federated security scenarios. Note: Although there
isn t an out-of-the-box context-aware binding to support federation, in theory,
a custom binding can be created to support context. In this situation it can be
an issue if the token is not available to the Workflow thread. Now that you understand the threading model and related
identities, we can discuss how Workflow Services implement role-based security.
To configure role-based security for ReceiveActivity, set values in the
Permissions tab of the Choose Operation dialog box (see Figure 7), which is
presented when you configure the ServiceOperationInfo property for the
ReceiveActivity. You can specify a particular user name (not very useful)
or a list of roles (only useful if you are doing role-based security and not
claims-based security). The role-based check is executed using the
Thread.CurrentPrincipal.IsInRole function, and thus expects there to be an
appropriate security principal attached to the thread. In the case of Windows
credentials, this is automatic. In the case of UserName, Certificate, or
IssuedToken credentials, you must provide an appropriate authorization behavior
and policy to create a security principal for the WCF thread. The ASP.NET role
provider handles this for you; otherwise, you re on your own. Figure 6 shows
how to create a security principal for the WCF thread using a custom
authorization policy. If the role-based check passes, the ReceiveActivity
sequence is executed on a new thread. At this point custom validation code will
be executed, if present, giving you another opportunity to validate the call
before execution of the sequence. Each ReceiveActivity has an OperationValidation property
(which is also exposed as an OperationValidation event) that can be used to
provide custom validation code for the ReceiveActivity. Figure 7 illustrates a
custom handler named ValidateCaller. This code is called after role-based
security checks for the ReceiveActivity are completed. This code can only
access the ServiceSecurityContext to validate claims because the security
principal attached to the thread is no longer representative of the original
caller. Outgoing calls from a Workflow Service to downstream WCF
services must be secured according to downstream service requirements. Each
SendActivity is associated with an endpoint configuration that is used to
initialize a dynamically generated proxy used to call a service operation. The
credentials required by this endpoint configuration must somehow be provided to
the proxy during its initialization. Unlike traditional client applications
that manually construct their proxies and set credentials in, Workflow Services
require some creative techniques to achieve the same result. Several options exist to initialize the dynamically
generated proxy with the required credentials. Figure 8 lists common credential
types, the default behavior of the dynamic proxy resulting from the endpoint
configuration, and possible initialization techniques for the proxy. Credential Type Proxy Default Proxy
Initialization Windows Proxy is initialized with the host process identity. Can provide alternate Windows credentials using an
endpoint behavior. UserName Proxy is not initialized with a UserName credential. Can provide a UserName credential at run time using an
endpoint behavior. Certificate Proxy is not initialized with a Certificate credential. Can provide a client certificate statically or at run time
using an endpoint behavior. IssuedToken Proxy is not initialized with an IssuedToken credential.
Can provide an IssuedToken at run time using an endpoint
behavior. Figure 8: Typical
credential types and associated proxy initialization behaviors. If a Windows credential is required by the SendActivity
endpoint configuration, the host process credential is supplied to the proxy.
This is a desirable result if Workflow Service is a middle-tier service in a
trusted subsystem model, where downstream services need not know about the
original caller s identity. There is no way to impersonate the originating
caller, but, as I said, this is not a deficit in most cases (impersonation is
not usually a good idea). It is not likely that a different Windows credential
is required in this scenario, but it is possible to set a different credential
at run time using an endpoint behavior. I ll discuss this option alongside
UserName credentials. Certificate credentials are also typically part of a
trusted subsystem model, where a specific client certificate is used to
authenticate the middle-tier code and authorize its access. In this case, a
hard-coded certificate is acceptable. For example, the following storeLocation="CurrentUser"
storeName="My" x509FindType="FindBySubjectName"/> Like the Windows credential, a certificate can be provided
at run time using endpoint behaviors. UserName credentials should be supplied
at run time. This can be done in two ways: The latter of these two techniques is not particularly
difficult, but it does require several points of WCF extensibility that are out
of the scope of this article. Providing a custom ClientCredentials type can be
done by supplying the type name to the endpoint behavior, as shown in Figure 9. binding="netTcpBinding"
bindingConfiguration="netTcpUserName" behaviorConfiguration="UserNameClientBehavior" contract="UserNameService.IAuthenticationService"
name="UserNameService"> Figure 9: Client
endpoint configuration with custom ClientCredentials type. The implementation of the ClientCredentials type need only
provide the UserName credentials in the ApplyClientBehavior override, as shown in
Figure 10 (hard-coded for simplicity). public class CustomClientCredentials: ClientCredentials { public
CustomClientCredentials(): base() {} public
CustomClientCredentials(ClientCredentials credentials): base(credentials) {} protected override
ClientCredentials CloneCore() { return new
CustomClientCredentials(this); } public override void
ApplyClientBehavior(ServiceEndpoint serviceEndpoint, System.ServiceModel.Dispatcher.ClientRuntime
behavior) { this.UserName.UserName
= "username"; this.UserName.Password
= "password"; base.ApplyClientBehavior(serviceEndpoint, behavior); } } Figure 10: A
simple implementation of the ClientCredentials type. In a similar fashion, a custom Windows, Certificate, or
IssuedToken credential also can be supplied although it is not likely that an
IssuedToken would be handled this way. IssuedToken scenarios typically imply federation. A few
things get in the way of federation scenarios and Workflow Services: To properly secure Workflow Services it is important to
understand how they differ from traditional WCF services. Specifically,
understanding the threading model and how to pass information to the Workflow
thread, and understanding how the dynamic proxy can be initialized according to
downstream service requirements. In a trusted subsystem model, it should not be
an issue that the credentials used to authenticate to a ReceiveActivity aren t
available to the Workflow thread, and that the dynamic proxy uses the host
process identity. If further customization is required, some tips have been
provided in this article; you can explore more of these details in the accompanying
code. Download the samples
for this article at http://www.dasblonde.net/downloads/asppronov08.zip. Michele Leroux
Bustamante is Chief Architect of IDesign Inc., Microsoft Regional Director
for San Diego, and Microsoft MVP for Connected Systems. At IDesign Michele
provides training, mentoring, and high-end architecture consulting services
focusing on Web services, scalable and secure architecture design for .NET,
federated security scenarios, Web services, interoperability, and globalization
architecture. She is a member of the International .NET Speakers Association
(INETA), a frequent conference presenter, conference chair for SD West, and is
frequently published in several major technology journals. Michele also is on
the board of directors for IASA (International Association of Software
Architects), and a Program Advisor to UCSD Extension. Her latest book is Learning WCF (O Reilly 2007); visit her book
blog at http://www.thatindigogirl.com.
Reach her at mailto:[email protected], or
visit http://www.idesign.net and her main
blog at http://www.dasblonde.net.
Threads, Identities, and ServiceSecurityContext
Figure 5: Workflow Services, process
identities, and security principals.Configuring Role-based Security
Figure 7: Configuring role-based
security permissions for the ReceiveActivity. OperationValidation
Security and SendActivity
Conclusion