Important
Reference documentation is available at https://styrainc.github.io/opa-csharp
You can use the Styra OPA SDK to connect to Open Policy Agent and Enterprise OPA deployments.
dotnet add package Styra.Opa
The following examples assume an OPA server at http://localhost:8181
equipped with the following Rego policy in authz.rego
:
package authz
import rego.v1
default allow := false
allow if input.subject == "alice"
and this data.json
:
{
"roles": {
"admin": ["read", "write"]
}
}
For a simple boolean response with input, use the SDK as follows:
using Styra.Opa;
string opaUrl = "http://localhost:8181";
OpaClient opa = new OpaClient(opaUrl);
var input = new Dictionary<string, object>() {
{"subject", "alice"},
{"action", "read"},
};
bool allowed = false;
try
{
allowed = await opa.Check("authz/allow", input);
}
catch (OpaException e)
{
Console.WriteLine("exception while making request against OPA: " + e);
}
Console.WriteLine("allowed: " + allowed);
Result
allowed: True
The .Evaluate()
method can be used instead of .Check()
for non-boolean output types:
using System;
using System.Collections.Generic;
using System.Linq;
using Styra.Opa;
var opaUrl = "http://localhost:8181";
var opa = new OpaClient(opaUrl);
var input = new Dictionary<string, string>() {
{"subject", "alice"},
{"action", "read"},
};
var result = new Dictionary<string, List<string>>();
try
{
result = await opa.Evaluate<Dictionary<string, List<string>>>("roles", input);
}
catch (OpaException e)
{
Console.WriteLine("exception while making request against OPA: " + e.Message);
}
Console.WriteLine("content of data.roles:");
foreach (var pair in result)
{
Console.Write(" {0} => [ ", pair.Key);
foreach (var item in pair.Value)
{
Console.Write("{0} ", item);
}
Console.WriteLine("]");
}
Result
content of data.roles:
admin => [ read write ]
For evaluating the default rule (configured with your OPA service), use EvaluateDefault
. input
is optional, and left out in this example:
using Styra.Opa;
string opaUrl = "http://localhost:8181";
OpaClient opa = new OpaClient(opaUrl);
bool allowed = false;
try {
allowed = await opa.EvaluateDefault<bool();
}
catch (OpaException e) {
Console.WriteLine("exception while making request against OPA: " + e);
}
Console.WriteLine("allowed: " + allowed);
Result
allowed: False
Enterprise OPA supports executing many queries in a single request with the Batch API.
The OPA C# SDK has native support for Enterprise OPA's batch API, with a fallback behavior of sequentially executing single queries if the Batch API is unavailable (such as with open source Open Policy Agent).
using Styra.Opa;
string opaUrl = "http://localhost:8181";
OpaClient opa = new OpaClient(opaUrl);
var input = new Dictionary<string, Dictionary<string, object>>() {
{ "AAA", new Dictionary<string, object>() { { "subject", "alice" }, { "action", "read" } } },
{ "BBB", new Dictionary<string, object>() { { "subject", "bob" }, { "action", "write" } } },
{ "CCC", new Dictionary<string, object>() { { "subject", "dave" }, { "action", "read" } } },
{ "DDD", new Dictionary<string, object>() { { "subject", "sybil" }, { "action", "write" } } },
};
OpaBatchResults results = new OpaBatchResults();
OpaBatchErrors errors = new OpaBatchErrors();
try
{
(results, errors) = await opa.EvaluateBatch("authz/allow", input);
}
catch (OpaException e)
{
Console.WriteLine("exception while making request against OPA: " + e.Message);
}
Console.WriteLine("Query results, by key:");
foreach (var pair in results)
{
Console.WriteLine(" {0} => {1}", pair.Key, pair.Value.Result.Boolean);
}
if (errors.Count > 0)
{
Console.WriteLine("Query errors, by key:");
foreach (var pair in errors)
{
Console.WriteLine(" {0} => {1}", pair.Key, pair.Value);
}
}
Result
Query results, by key:
AAA => True
BBB => False
CCC => False
DDD => False
See the API Documentation for reference on the properties and types available from a result.
Using the OPA C# SDK, it can be more natural to use custom class types as inputs and outputs to a policy, rather than System.Collections.Dictionary
(or Collections.List
). Internally, the OPA C# SDK uses Newtonsoft.Json
to serialize and deserialize inputs and outputs JSON to the provided types.
In the example below, note:
- Using an
enum
for an input field - Hiding the sensitive
UUID
with theJsonIgnore
property - Deserializing the query response to a
bool
using System;
using Styra.Opa;
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
namespace Application
{
class Program
{
public enum ActionType
{
invalid,
create,
read,
update,
delete
}
private class CustomRBACObject
{
[JsonProperty("user")]
public string User = "";
[JsonProperty("action")]
[JsonConverter(typeof(StringEnumConverter))]
public ActionType Action = ActionType.invalid;
[JsonIgnore]
public string UUID = System.Guid.NewGuid().ToString();
public CustomRBACObject() { }
public CustomRBACObject(string user, ActionType action)
{
User = user;
Action = action;
}
}
static async Task<int> Main(string[] args)
{
string opaUrl = "http://localhost:8181";
OpaClient opa = new OpaClient(opaUrl);
var input = new CustomRBACObject("bob", ActionType.read);
Console.WriteLine("The JSON that OPA will receive: {{\"input\": {0}}}", JsonConvert.SerializeObject(input));
bool allowed = false;
try
{
allowed = await opa.Evaluate<bool>("authz/allow", input);
}
catch (OpaException e)
{
Console.WriteLine("exception while making request against OPA: " + e.Message);
}
Console.WriteLine("allowed: " + allowed);
return 0;
}
}
}
Result
The JSON that OPA will receive: {"input": {"user":"bob","action":"read"}}
allowed: False
The OPA C# SDK uses opt-in, compile-time source generated logging, which can be integrated as a part of the overall logs of a larger application.
Here's a quick example:
using Microsoft.Extensions.Logging;
using Styra.Opa;
internal class Program
{
static async Task<int> Main(string[] args)
{
using ILoggerFactory factory = LoggerFactory.Create(builder => builder.AddConsole());
ILogger<OpaClient> logger = factory.CreateLogger<OpaClient>();
var opaURL = "http://localhost:8181";
OpaClient opa = new OpaClient(opaURL, logger);
logger.LogInformation("Initialized an OPA client for the OPA at: {Description}.", opaURL);
var allow = await opa.Evaluate<bool>("this/rule/does/not/exist", false);
return 0;
}
}
Result
info: Styra.Opa.OpaClient[0]
Initialized an OPA client for the OPA at: http://localhost:8181.
warn: Styra.Opa.OpaClient[2066302899]
executing policy at 'this/rule/does/not/exist' succeeded, but OPA did not reply with a result
Unhandled exception. OpaException: executing policy at 'this/rule/does/not/exist' succeeded, but OPA did not reply with a result
...
Note
For low-level SDK usage, see the sections below.
For more information about the API: Enterprise OPA documentation
using Styra.Opa.OpenApi;
using Styra.Opa.OpenApi.Models.Components;
var sdk = new OpaApiClient();
var res = await sdk.ExecuteDefaultPolicyWithInputAsync(
input: Input.CreateNumber(
4963.69D
),
pretty: false,
acceptEncoding: GzipAcceptEncoding.Gzip
);
// handle response
using Styra.Opa.OpenApi;
using Styra.Opa.OpenApi.Models.Requests;
var sdk = new OpaApiClient();
ExecutePolicyWithInputRequest req = new ExecutePolicyWithInputRequest() {
Path = "app/rbac",
RequestBody = new ExecutePolicyWithInputRequestBody() {
Input = Input.CreateBoolean(
false
),
},
};
var res = await sdk.ExecutePolicyWithInputAsync(req);
// handle response
using Styra.Opa.OpenApi;
using Styra.Opa.OpenApi.Models.Components;
using Styra.Opa.OpenApi.Models.Requests;
using System.Collections.Generic;
var sdk = new OpaApiClient();
ExecuteBatchPolicyWithInputRequest req = new ExecuteBatchPolicyWithInputRequest() {
Path = "app/rbac",
RequestBody = new ExecuteBatchPolicyWithInputRequestBody() {
Inputs = new Dictionary<string, Input>() {
{ "key", Input.CreateStr(
"<value>"
) },
},
},
};
var res = await sdk.ExecuteBatchPolicyWithInputAsync(req);
// handle response
Available methods
- ExecuteDefaultPolicyWithInput - Execute the default decision given an input
- ExecutePolicy - Execute a policy
- ExecutePolicyWithInput - Execute a policy given an input
- ExecuteBatchPolicyWithInput - Execute a policy given a batch of inputs
- CompileQueryWithPartialEvaluation - Partially evaluate a query
- Health - Verify the server is operational
The default server can be overridden globally by passing a URL to the serverUrl: string
optional parameter when initializing the SDK client instance. For example:
using Styra.Opa.OpenApi;
using Styra.Opa.OpenApi.Models.Components;
var sdk = new OpaApiClient(serverUrl: "http://localhost:8181");
var res = await sdk.ExecuteDefaultPolicyWithInputAsync(
input: Input.CreateNumber(
4963.69D
),
pretty: false,
acceptEncoding: GzipAcceptEncoding.Gzip
);
// handle response
Handling errors in this SDK should largely match your expectations. All operations return a response object or throw an exception.
By default, an API error will raise a Styra.Opa.OpenApi.Models.Errors.SDKException
exception, which has the following properties:
Property | Type | Description |
---|---|---|
Message |
string | The error message |
StatusCode |
int | The HTTP status code |
RawResponse |
HttpResponseMessage | The raw HTTP response |
Body |
string | The response content |
When custom error responses are specified for an operation, the SDK may also throw their associated exceptions. You can refer to respective Errors tables in SDK docs for more details on possible exception types for each operation. For example, the ExecuteDefaultPolicyWithInputAsync
method throws the following exceptions:
Error Type | Status Code | Content Type |
---|---|---|
Styra.Opa.OpenApi.Models.Errors.ClientError | 400, 404 | application/json |
Styra.Opa.OpenApi.Models.Errors.ServerError | 500 | application/json |
Styra.Opa.OpenApi.Models.Errors.SDKException | 4XX, 5XX | */* |
using Styra.Opa.OpenApi;
using Styra.Opa.OpenApi.Models.Components;
using Styra.Opa.OpenApi.Models.Errors;
var sdk = new OpaApiClient();
try
{
var res = await sdk.ExecuteDefaultPolicyWithInputAsync(
input: Input.CreateNumber(
4963.69D
),
pretty: false,
acceptEncoding: GzipAcceptEncoding.Gzip
);
// handle response
}
catch (Exception ex)
{
if (ex is ClientError)
{
// Handle exception data
throw;
}
else if (ex is Models.Errors.ServerError)
{
// Handle exception data
throw;
}
else if (ex is Styra.Opa.OpenApi.Models.Errors.SDKException)
{
// Handle default exception
throw;
}
}
This SDK supports the following security scheme globally:
Name | Type | Scheme |
---|---|---|
BearerAuth |
http | HTTP Bearer |
To authenticate with the API the BearerAuth
parameter must be set when initializing the SDK client instance. For example:
using Styra.Opa.OpenApi;
using Styra.Opa.OpenApi.Models.Components;
var sdk = new OpaApiClient(bearerAuth: "<YOUR_BEARER_TOKEN_HERE>");
var res = await sdk.ExecuteDefaultPolicyWithInputAsync(
input: Input.CreateNumber(
4963.69D
),
pretty: false,
acceptEncoding: GzipAcceptEncoding.Gzip
);
// handle response
For questions, discussions and announcements related to Styra products, services and open source projects, please join the Styra community on Slack!