Distributed Evaluation
From its first release, Enforcer runs in-process with the application requiring authorization decisions. As the policy store can be distributed, this does give a flexible model. However, some customers have told us they wanted the ability to have Enforcer running separately and have many parts of the application call it remotely; this is particularly true for those using microservice architectures and containerization or hetrogenoius technologies for their systems.
As of Enforcer 2.0, you can now host Enforcer in its own Asp.Net Core application and invoke the authorization engine over HTTP. This section of the documentation describes how to configure Enforcer for remote evaluation and the different options that can be configured.
Configuring the server
To configure a server to host Enforcer for remote evaluation is very similar to hosting in-process. The only difference is that you must tell Enforcer where it should listen for evaluation requests and enfure that Enforcer is running in the request pipeline. Support for server-side remote evaluation is contained in the Rsk.Enforcer.Remote.Hosting
package.
Once referenced, Rsk.Enforcer.Remote.Hosting
introduces two new extension methods WithRemoteEvaluation
to configure the necessary dependency injection requirement and UseEnforcerRemoteHosting
to inject middleware into the processing pipeline that listens for, and executes, evaluation requests. Here is an example of their use:
var builder = WebApplication.CreateBuilder(args);
builder.Services
.AddEnforcer("AcmeCorp.Global", options =>
{
options.Licensee = <Licensee>;
options.LicenseKey = <License Key>
})
.AddFileSystemPolicyStore("policies")
.AddPolicyAttributeProvider<FinanceDepartmentAttributeProvider>()
.WithRemoteHosting(options =>
{
options.BaseUrl = "pdp";
});
var app = builder.Build();
app.UseEnforcerRemoteHosting();
app.Run();
In the above example, requests to the server with the path "pdp" are treated as evaluation requests and intercepted by the remote evaluation middleware.
Configuring the client
In the client side, Enforcer is configured very similarly to running the authorization engine in-process. However in your startup code, instead of calling AddEnforcer
you call AddRemoteEnforcer
and provide the endpoint on which the server is listening. There is no need to define the location of the policy store as this is configured on the server.
services
.AddRemoteEnforcer(o =>
{
}, r =>
{
r.EndpointAddress = "http://localhost:5132/pdp";
})
.AddPolicyEnforcementPoint(o => o.Bias = PepBias.Deny)
.AddClaimsAttributeValueProvider(o => { })
.AddDefaultAdviceHandling()
.AddEnforcerAuthorizationRazorViewDenyHandler<GeneralViewDenyHandler>();
How evaluation is performed
In standard configuration, the attributes used in evaluation are those that are available to the policy engine via attribute value providers and those passed in explicitly via the request context. With remote evaluation many attributes are not implicitly available to the server (for example, claims in an Asp.Net Core application). Enforcer 2.0 introduces a new interface, IBulkAttributeProvider
. Attribute value providers that implement this interface are queried for their attributes when the request is sent to thte server. This allows a much richer request context to be sent to the server than would normally be available. IBulkAttributeProvider
is implemented by the following out-of-the-box attribute value providers:
- DynamicAttributeValueProvider
- RecordAttributeValueProvider<T>
- DelegatingRecordAttributeValueProvider<T>
- AuthorizationContext<T>
- EnvironmentAttributeValueProvider
- ActionExecutingContextAttributeValueProvider
- ClaimsAttributeValueProvider
You must implement IBulkAttributeProvider
on your custom attribute value providers if you want their atributes to be sent over the wire automatically.
Supressing the remoting of attributes
There may be attributes that you do not want sent to the server. If you want to use the server's clock rather than the clients for time based attributes, then set OmitStandardPips
to true
when configuring the DistributedEnforcerOptions
in the call to AddRemoteEnforcer
. However, there may be specific attributes that you do not want sent to the server (sensitive claim values for example) and in this case you can add the attributes to the NonRemotableAttributes
collection in the DistributedEnforcerOptions
configured in the call to AddRemoteEnforcer
. By default the only attribute that is automatically supressed is the AspNet.Identity.SecurityStamp
from the claims collection. If you do want this claim sent to the server then call Clear
on the NonRemotableAttributes
collection before adding any other values to it.
Securing the remote policy evaluation
Remote invocation of the Enforcer authorization engine supports both authentication and authorization.
Configuring authentication on the server
Authentication integrates with the AspNet Core authentication service. To enable authentication, set the AuthenticationState
of the hosting options to EnforcerAuthenticationState.Authenticated
. Once this has been set, the AuthenticationScheme
specified in the hosting options will be used to drive authentication. If no value is set then the default scheme will be used.
Integrating with AspNet Core allows you to use the mechanism that works best for you, whether it be OAuth2, API keys, basic HTTP authentication or something else.
Configuring authorization on the server
Authorization integrates with the AspNet Core authorization service. Authorization will be performed if you specify an AuthorizationPolicy
in the Enforcer hosting options. If none is specified then no authorization takes place.
Configuring authentication on the client
Because the server is flexible in the way it can be configured for authentication, the same has to be true for the client. On the client, control is given over to your code to modify the HttpClient
that is used for the authorization request. Your code is then informed about the success or failure of that attempted request. To take control of this mechanism you create an implementation of IEnforcerHttpAuthenticationHandler
. This is defined as follows:
public interface IEnforcerHttpAuthenticationHandler
{
Task ModifyClient(HttpClient client);
Task<RetryBehavior> OnRequestFailure(HttpStatusCode statusCode, string message);
Task OnRequestSuccess();
}
public enum RetryBehavior
{
None,
Retry
}
ModifyClient
is called before the evaluation request is made to the server and allows you to, for example, set the HTTP authorization header for the request.
OnRequestFailure
is called if the request to the server fails and is passed the failure status code and hte body of the response as a string. This gives you the opportunity to, say, refresh a cached OAuth token which has expired as the server returned a 401 status code. You return whether or not the request should be reattempted. If you return Retry
then ModifyClient
will be called once more before the evaluation request is resent to the server.
OnRequestSuccess
is called if the server returns an HTTP success code. This allows you to reset a retry count or similiar.
You enable your handler by passing it to the EnforcerAuthenticationHandler
member of the HttpRemoteEvaluationOptions
you configure.
A note of caution
The Enforcer HTTP authentication handler is a singleton. Therefore, multiple evaluation requests can call it concurrently. You should be prepared for your handler to be called on multiple concurrent threads and so any member level state should be managed in a thread-safe way.
The remote evaluation wire protocol over HTTP
This section describes how to perform remote evaluation over HTTP. It describes the API endpoint, media types and payload structures of both request and response.
HTTP Remote evaluation request
PUT <host>/base url>
where <host>
is the host name of the remote server and <base url>
is the value specific in the server's EnforcerHostingOptions.BaseUrl
.
Content Type: application/vnd.rsk.enforcer.evaluation-request-v1+json
Paylolad:
{
"attributes":[
{
"name": "ResourceType",
"category": "urn:oasis:names:tc:xacml:3.0:attribute-category:resource",
"type": "string",
"sensitivity": "NonSensitive",
"values":["purchaseOrder"]
},
{
"name": "Action",
"category": "urn:oasis:names:tc:xacml:3.0:attribute-category:action",
"type": "string",
"sensitivity": "NonSensitive",
"values":["add"]
},
{
"name": "sub",
"category": "urn:oasis:names:tc:xacml:3.0:attribute-category:subject",
"type": "string",
"sensitivity": "PII",
"values":["1209021-123894u832y4-2130987410870-124321"]
},
{
"name": "role",
"category": "urn:oasis:names:tc:xacml:3.0:attribute-category:subject",
"type": "string",
"sensitivity": "PII",
"values":["Employee", "Manager"]
}
]
}
Attribute type values
The table below describes each of the permitted values for attrubute type
Value | Description |
---|---|
string | Unicode text value |
integer | 64 bit integer value |
double | Double precision floating point |
boolean | Boolean value, true or false |
datetime | A value containing both date and time with timezone offset in the form yyyy-MM-ddThh:mm:ssZ |
date | A date value with no time element in the form yyyy-MM-dd |
time | A time value with no date element in the form hh:mm:ss |
duration | A value describing a time duration in the form hh:mm:ss |
Attribute sensitivity values
The following table describes the sensivity of the values being described. This will affect whether those values can be logged during evaluation
Value | Description |
---|---|
PII | Personally Identifiable Information. This information could specifically identify an individual and is the most sensitive |
Sensitive | This information could be used by an attacker but cannot be used to identify a specific individual |
NonSensitive | This information is not deemed sensitive |
HTTP Remote evaluation reponse
Content Type: application/vnd.rsk.enforcer.evaluation-response-v1+json
Paylolad:
{
"outcome": "deny",
"advice":[
{
{
"name": "denyReason".
"attributes"[
{
"name": "reason",
"category": "evaluationResult",
"type": "string",
"sensitivity": "NonSensitive",
"values":["User does not have permission to create perchase order of specified amount"]
},
{
"name": "amount",
"category": "purchaseOrders",
"type": "double",
"sensitivity": "NonSensitive",
"values":[1023.55]
}
]
}
},
"obligations":[
{
{
"name": "audit".
"attributes"[
{
"name": "description",
"category": "audit",
"type": "string",
"sensitivity": "nonSensitive",
"values":["Denied user attempt to create perchase order of specified amount"]
},
{
"name": "amount",
"category": "purchaseOrders",
"type": "double",
"sensitivity": "nonSensitive",
"values":[1023.55]
},
{
"name": "sub",
"category": "urn:oasis:names:tc:xacml:3.0:attribute-category:subject",
"type": "string",
"sensitivity": "PII",
"values":["1209021-123894u832y4-2130987410870-124321"]
}
]
}
}
]
}
Policy outcome values
The table below describes each of the permitted values for attrubute type
Value | Description |
---|---|
permit | The request have been evaluated to permit access |
deny | The request have been evaluated to permit access |
indeterminate | An issue in evaluation mean't that thepolicy was unable to provide a definitive result |
notApplicable | No policy elements were evaluated as none met the conditions of the supplied attributes |
A note on implementation
When a policy returns obligations, the caller must understand what these mean and take appropriate action. If any are not understood or cannot be actioned, then the outcome of the evaluation should be treated as indeterminate
.
If the outcome of a policy evaluation is indeterminate
(either directly or implcitly due to non-actioned obligations) or notApplicable
then the notion of PEP Bias must apply and the caller should consistently treat the outcome as permit
or deny
(deny
being most common).