Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

setting and reading custom options #600

Closed
bumberboy opened this issue Oct 3, 2023 · 5 comments
Closed

setting and reading custom options #600

bumberboy opened this issue Oct 3, 2023 · 5 comments

Comments

@bumberboy
Copy link

Hello there

Thanks for building this!

I'm looking to set some custom options on my rpcs:

import "google/protobuf/descriptor.proto";

extend google.protobuf.MethodOptions {
  optional string abc = 51234;
}

service XXXService {
  rpc Xxx(XxReq) returns (XxResp) {
    option (abc) = "hello, world";
  }

}

And be able to read them within an interceptor. The intention is to build an authorization interceptor that checks the custom option on the rpc on request to run the right ACL auth validation. Any idea how I can achieve this?

Thanks in advance!

@emcfarlane
Copy link
Contributor

As an example it might be good to have a look at: https://github.com/akshayjshah/connectauth

Can use context to propagate auth values.

@jhump
Copy link
Member

jhump commented Oct 3, 2023

@bumberboy, there is some discussion in #523 related to accessing descriptors from interceptors. I think it is likely we will add an accessor for a method's schema (likely in the form of a protoreflect.MethodDescriptor) to the Spec type.

But until then, an interceptor can get the schema in the following fashion:

func (myInterceptor) WrapUnary(fn connect.UnaryFunc) connect.UnaryFunc {
    return func(ctx context.Context, req connect.AnyRequest) (connect.AnyResponse, error) {
        methodName := strings.ReplaceAll(strings.TrimPrefix(req.Spec().Procedure, "/"), "/", ".")
        desc, err := protoregistry.GlobalFiles.FindDescriptorByName(protoreflect.FullName(methodName))
        if err != nil {
            return connect.NewError(connect.CodeInternal,
                       fmt.Errorf("could not find descriptor for method %s", req.Spec().Procedure))
        }
        methodDesc, ok := desc.(protoreflect.MethodDescriptor)
        if !ok {
            return connect.NewError(connect.CodeInternal,
                       fmt.Errorf("descriptor for method %s does not implement protoreflect.MethodDescriptor: %T",
                           req.Spec().Procedure, desc,
                       ))
        }
        // Do something with methodDesc, like examining its Options().
        // Can get service descriptor via methodDesc.Parent().
        return fn(ctx, req)
    }
}
// Can do something similar in WrapStreamingHandler for streaming endpoints.

Admittedly, this is a bit convoluted and also not terribly efficient. There are likely some tricks to make it more efficient, like something like a sync.Once to compute an index of all registered services and their descriptors for faster lookup. But that is complicated by the circular nature -- you need the interceptor to construct the Connect handler, but you need the Connect handlers (or at least all of the service names) to build an index for the interceptor.

@bumberboy
Copy link
Author

bumberboy commented Oct 3, 2023

@emcfarlane Thanks for the suggestion! That looks perfect for authentication (and I've already implemented something similar for that).

I'm looking to implement authorization now, where I try to answer the question: can this user perform this action (READ, WRITE, DELETE) on this resource?

I'm intending to annotate each rpc that requires authorization with a resource and an action, and then within my authorization interceptor, make a call to an external service that determines if the user has the right permissions. I'm looking to use spicedb from authzed for the auth service.

@jeffmccune
Copy link

I'm working on a similar use case Ed asked me to provide feedback on. I'd like to include primitive roles (owner, editor, viewer) to start my project and check "has rbac" off the list, then potentially add fine grained permissions later if customers need it.

I've got an ID token with a groups claim, which is a json array of strings. Groups in this specific case is really a role and may be a different claim depending on the id provider. Keycloak for example has a role claim.

It would buy a lot of runway early in the project if I could tag each service method as allowing messages from one of owners, editors, or viewers.

One specific use case I've been thinking of: Given a PatchUser(UserRequest) UserResponse method, allow only requests with user.email == idToken.email so that users cannot set their email to arbitrary values.

@jhump
Copy link
Member

jhump commented Nov 9, 2023

This should be addressed by #629.

@jhump jhump closed this as completed Nov 9, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants