OPA C# SDK Usage
Installation
Nuget:
dotnet add package Styra.Opa
Code Examples
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"]
}
}
Simple Query
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
Simple Query with Output
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 ]
Default Rule
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
Batched Queries
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.
- See Batch Query Responses for more information on batch query response values.
Using Custom Classes for Input and Output
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
Integrating logging with the OPA C# SDK
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
...