Skip to content

Commit

Permalink
enhancement: Metadata (headers) support (#115)
Browse files Browse the repository at this point in the history
### Summary

- Added gRPC Metadata support
- Simplified the internal client-building logic

### Example

When metadata (`{"wibble": "wobble"}`) is provided during both client
initialization and CheckResources call, the cerbos audit logs show;
```
{
  "log.logger":"cerbos.audit",
  "log.kind":"access",
  "timestamp":"2023-12-13T17:26:56.614190Z",
  "callId":"01HHJ3F8B657J3PB3S6P21YY3X",
  "peer":{
    "address":"127.0.0.1:50993",
    "userAgent":"grpc-dotnet/2.59.0 (.NET 6.0.5; CLR 6.0.5; net6.0; osx; x64)"
  },
  "metadata":{
    "wibble":{
      "values":[
        "wobble, wobble"
      ]
    }
  },
  "method":"/cerbos.svc.v1.CerbosService/CheckResources"
}
```

---------

Signed-off-by: Oğuzhan Durgun <[email protected]>
  • Loading branch information
oguzhand95 authored Dec 14, 2023
1 parent 7cdf828 commit fc5f369
Show file tree
Hide file tree
Showing 4 changed files with 86 additions and 55 deletions.
20 changes: 11 additions & 9 deletions src/Sdk.UnitTests/Cerbos/Sdk/UnitTests/CerbosClientTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ public class CerbosClientTest
private const string Tag = "dev";
private const string PathToPolicies = "./../../../res/policies";
private const string PathToConfig = "./../../../res/config";
private readonly Grpc.Core.Metadata _metadata = new() { { "wibble", "wobble" } };

private IContainer? _container;

private CerbosClient? _client;
Expand All @@ -45,8 +47,8 @@ public void OneTimeSetUp()

Task.Run(async () => await _container.StartAsync()).Wait();
Thread.Sleep(3000);
_client = CerbosClientBuilder.ForTarget("http://127.0.0.1:3593").WithPlaintext().Build();
_clientPlayground = CerbosClientBuilder.ForTarget(PlaygroundHost).WithPlaygroundInstance(PlaygroundInstanceId).Build();
_client = CerbosClientBuilder.ForTarget("http://127.0.0.1:3593").WithMetadata(_metadata).WithPlaintext().Build();
_clientPlayground = CerbosClientBuilder.ForTarget(PlaygroundHost).WithMetadata(_metadata).WithPlaygroundInstance(PlaygroundInstanceId).Build();
}

[OneTimeTearDown]
Expand Down Expand Up @@ -81,7 +83,7 @@ public void CheckWithoutJwt()
)
.WithIncludeMeta(true);

var have = _client.CheckResources(request).Find("XX125");
var have = _client.CheckResources(request, _metadata).Find("XX125");
Assert.That(have.IsAllowed("view:public"), Is.True);
Assert.That(have.IsAllowed("approve"), Is.False);

Expand Down Expand Up @@ -126,7 +128,7 @@ public void CheckWithJwt()
.WithActions("defer")
);

var have = _client.CheckResources(request).Find("XX125");
var have = _client.CheckResources(request, _metadata).Find("XX125");
Assert.That(have.IsAllowed("defer"), Is.True);
}

Expand Down Expand Up @@ -175,7 +177,7 @@ public void CheckMultiple()
);


var have = _client.CheckResources(request);
var have = _client.CheckResources(request, _metadata);
var resourcexx125 = have.Find("XX125");
Assert.That(resourcexx125.IsAllowed("view:public"), Is.True);
Assert.That(resourcexx125.IsAllowed("defer"), Is.True);
Expand Down Expand Up @@ -213,7 +215,7 @@ public void PlanResources()
.WithAction("approve");


var have = _client.PlanResources(request);
var have = _client.PlanResources(request, _metadata);
Assert.That(have.Action, Is.EqualTo("approve"));
Assert.That(have.PolicyVersion, Is.EqualTo("20210210"));
Assert.That(have.ResourceKind, Is.EqualTo("leave_request"));
Expand Down Expand Up @@ -260,7 +262,7 @@ public void PlanResourcesValidation()
)
.WithAction("approve");

var have = _client.PlanResources(request);
var have = _client.PlanResources(request, _metadata);
Assert.That(have.Action, Is.EqualTo("approve"));
Assert.That(have.PolicyVersion, Is.EqualTo("20210210"));
Assert.That(have.ResourceKind, Is.EqualTo("leave_request"));
Expand Down Expand Up @@ -302,7 +304,7 @@ public void Playground()
.WithActions("approve", "delete")
);

var have = _clientPlayground.CheckResources(request).Find("XX125");
var have = _clientPlayground.CheckResources(request, _metadata).Find("XX125");
Assert.That(have.IsAllowed("approve"), Is.True);
Assert.That(have.IsAllowed("delete"), Is.True);
}
Expand Down Expand Up @@ -333,7 +335,7 @@ public async Task CheckWithoutJwtAsync()
.WithActions("approve", "view:public")
);

var have = (await _client.CheckResourcesAsync(request)).Find("XX125");
var have = (await _client.CheckResourcesAsync(request, _metadata)).Find("XX125");
Assert.That(have.IsAllowed("view:public"), Is.True);
Assert.That(have.IsAllowed("approve"), Is.False);

Expand Down
62 changes: 26 additions & 36 deletions src/Sdk/Cerbos/Sdk/Builder/CerbosClientBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@

using System;
using System.IO;
using System.Threading.Tasks;
using Grpc.Core;
using Grpc.Core.Interceptors;
using Grpc.Net.Client;

namespace Cerbos.Sdk.Builder
Expand All @@ -20,6 +20,7 @@ public sealed class CerbosClientBuilder
private StreamReader TlsCertificate { get; set; }
private StreamReader TlsKey { get; set; }
private GrpcChannelOptions GrpcChannelOptions { get; set; }
private Metadata Metadata { get; set; }

private CerbosClientBuilder(string target) {
Target = target;
Expand All @@ -30,6 +31,12 @@ public static CerbosClientBuilder ForTarget(string target)
return new CerbosClientBuilder(target);
}

public CerbosClientBuilder WithMetadata(Metadata headers)
{
Metadata = headers;
return this;
}

public CerbosClientBuilder WithPlaintext() {
Plaintext = true;
return this;
Expand Down Expand Up @@ -81,14 +88,15 @@ public CerbosClient Build()
);
}

CallCredentials callCredentials = null;
Metadata combined = Metadata;
if (!string.IsNullOrEmpty(PlaygroundInstanceId))
{
callCredentials = CallCredentials.FromInterceptor((context, metadata) =>
{
metadata.Add(PlaygroundInstanceHeader, PlaygroundInstanceId.Trim());
return Task.CompletedTask;
});
{
combined = Utility.Metadata.Merge(
Metadata,
new Metadata {
{ PlaygroundInstanceHeader, PlaygroundInstanceId.Trim() }
}
);
}

SslCredentials sslCredentials = null;
Expand All @@ -103,39 +111,21 @@ public CerbosClient Build()
sslCredentials = new SslCredentials(CaCertificate.ReadToEnd());
}
}

GrpcChannel channel;
if (Plaintext)
var grpcChannelOptions = GrpcChannelOptions ?? new GrpcChannelOptions();
if (sslCredentials != null)
{
if (GrpcChannelOptions != null)
{
channel = GrpcChannel.ForAddress(Target, GrpcChannelOptions);
}
else
{
channel = GrpcChannel.ForAddress(Target);
}
grpcChannelOptions.Credentials = sslCredentials;
}
else
else if (!Plaintext)
{
GrpcChannelOptions grpcChannelOptions = GrpcChannelOptions ?? new GrpcChannelOptions();
if (callCredentials != null && sslCredentials != null)
{
grpcChannelOptions.Credentials = ChannelCredentials.Create(sslCredentials, callCredentials);
}
else if (sslCredentials != null)
{
grpcChannelOptions.Credentials = sslCredentials;
}
else if (callCredentials != null)
{
grpcChannelOptions.Credentials = ChannelCredentials.Create(ChannelCredentials.SecureSsl, callCredentials);
}

channel = GrpcChannel.ForAddress(Target, grpcChannelOptions);
grpcChannelOptions.Credentials = ChannelCredentials.SecureSsl;
}

return new CerbosClient(new Api.V1.Svc.CerbosService.CerbosServiceClient(channel));
var grpcChannel = GrpcChannel
.ForAddress(Target, grpcChannelOptions)
.Intercept();
return new CerbosClient(new Api.V1.Svc.CerbosService.CerbosServiceClient(grpcChannel), combined);
}
}
}
23 changes: 13 additions & 10 deletions src/Sdk/Cerbos/Sdk/CerbosClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using System;
using System.Threading.Tasks;
using Cerbos.Sdk.Response;
using Grpc.Core;

namespace Cerbos.Sdk
{
Expand All @@ -13,20 +14,22 @@ namespace Cerbos.Sdk
public sealed class CerbosClient
{
private Api.V1.Svc.CerbosService.CerbosServiceClient CerbosServiceClient { get; }

public CerbosClient(Api.V1.Svc.CerbosService.CerbosServiceClient cerbosServiceClient)
private readonly Metadata _metadata;

public CerbosClient(Api.V1.Svc.CerbosService.CerbosServiceClient cerbosServiceClient, Metadata metadata = null)
{
CerbosServiceClient = cerbosServiceClient;
_metadata = metadata;
}

/// <summary>
/// Send a request consisting of a principal, resource(s) & action(s) to see if the principal is authorized to do the action(s) on the resource(s).
/// </summary>
public CheckResourcesResponse CheckResources(Builder.CheckResourcesRequest request)
public CheckResourcesResponse CheckResources(Builder.CheckResourcesRequest request, Metadata headers = null)
{
try
{
return new CheckResourcesResponse(CerbosServiceClient.CheckResources(request.ToCheckResourcesRequest()));
return new CheckResourcesResponse(CerbosServiceClient.CheckResources(request.ToCheckResourcesRequest(), Utility.Metadata.Merge(_metadata, headers)));
}
catch (Exception e)
{
Expand All @@ -37,12 +40,12 @@ public CheckResourcesResponse CheckResources(Builder.CheckResourcesRequest reque
/// <summary>
/// Send an async request consisting of a principal, resource(s) & action(s) to see if the principal is authorized to do the action(s) on the resource(s).
/// </summary>
public Task<CheckResourcesResponse> CheckResourcesAsync(Builder.CheckResourcesRequest request)
public Task<CheckResourcesResponse> CheckResourcesAsync(Builder.CheckResourcesRequest request, Metadata headers = null)
{
try
{
return CerbosServiceClient
.CheckResourcesAsync(request.ToCheckResourcesRequest())
.CheckResourcesAsync(request.ToCheckResourcesRequest(), Utility.Metadata.Merge(_metadata, headers))
.ResponseAsync
.ContinueWith(
r => new CheckResourcesResponse(r.Result)
Expand All @@ -57,11 +60,11 @@ public Task<CheckResourcesResponse> CheckResourcesAsync(Builder.CheckResourcesRe
/// <summary>
/// Obtain a query plan for performing the given action on the given resource kind.
/// </summary>
public PlanResourcesResponse PlanResources(Builder.PlanResourcesRequest request)
public PlanResourcesResponse PlanResources(Builder.PlanResourcesRequest request, Metadata headers = null)
{
try
{
return new PlanResourcesResponse(CerbosServiceClient.PlanResources(request.ToPlanResourcesRequest()));
return new PlanResourcesResponse(CerbosServiceClient.PlanResources(request.ToPlanResourcesRequest(), Utility.Metadata.Merge(_metadata, headers)));
}
catch (Exception e)
{
Expand All @@ -72,12 +75,12 @@ public PlanResourcesResponse PlanResources(Builder.PlanResourcesRequest request)
/// <summary>
/// Obtain a query plan for performing the given action on the given resource kind.
/// </summary>
public Task<PlanResourcesResponse> PlanResourcesAsync(Builder.PlanResourcesRequest request)
public Task<PlanResourcesResponse> PlanResourcesAsync(Builder.PlanResourcesRequest request, Metadata headers = null)
{
try
{
return CerbosServiceClient
.PlanResourcesAsync(request.ToPlanResourcesRequest())
.PlanResourcesAsync(request.ToPlanResourcesRequest(), Utility.Metadata.Merge(_metadata, headers))
.ResponseAsync
.ContinueWith(
r => new PlanResourcesResponse(r.Result)
Expand Down
36 changes: 36 additions & 0 deletions src/Sdk/Cerbos/Sdk/Utility/Metadata.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
namespace Cerbos.Sdk.Utility
{
public static class Metadata
{
public static Grpc.Core.Metadata Merge(Grpc.Core.Metadata first, Grpc.Core.Metadata second)
{
if (first == null && second == null)
{
return null;
}

if (first != null && second == null)
{
return first;
}

if (first == null)
{
return second;
}

Grpc.Core.Metadata combined = new Grpc.Core.Metadata();
foreach (var m in first)
{
combined.Add(m);
}

foreach (var m in second)
{
combined.Add(m);
}

return combined;
}
}
}

0 comments on commit fc5f369

Please sign in to comment.