Skip to main content

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
info

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 the JsonIgnore 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
...