| Tim Windelschmidt | 6d33a43 | 2025-02-04 14:34:25 +0100 | [diff] [blame^] | 1 | // Copyright The Monogon Project Authors. |
| 2 | // SPDX-License-Identifier: Apache-2.0 |
| 3 | |
| Serge Bazanski | 3379a5d | 2021-09-09 12:56:40 +0200 | [diff] [blame] | 4 | package rpc |
| 5 | |
| 6 | import ( |
| 7 | "fmt" |
| 8 | "regexp" |
| 9 | |
| 10 | "google.golang.org/grpc/codes" |
| 11 | "google.golang.org/grpc/status" |
| 12 | "google.golang.org/protobuf/proto" |
| 13 | "google.golang.org/protobuf/reflect/protoreflect" |
| 14 | "google.golang.org/protobuf/reflect/protoregistry" |
| 15 | |
| 16 | epb "source.monogon.dev/metropolis/proto/ext" |
| 17 | ) |
| 18 | |
| 19 | // methodInfo is the parsed information for a given RPC method, as configured by |
| 20 | // the metropolis.common.ext.authorization extension. |
| 21 | type methodInfo struct { |
| 22 | // unauthenticated is true if the method is defined as 'unauthenticated', ie. |
| 23 | // that all requests should be passed to the gRPC handler without any |
| 24 | // authentication or authorization performed. |
| 25 | unauthenticated bool |
| 26 | // need is a map of permissions that the caller needs to have in order to be |
| 27 | // allowed to call this method. If not empty, unauthenticated cannot be set to |
| 28 | // true. |
| 29 | need map[epb.Permission]bool |
| 30 | } |
| 31 | |
| 32 | var ( |
| 33 | // reMethodName matches a /some.service/Method string from |
| 34 | // {Stream,Unary}ServerInfo.FullMethod. |
| 35 | reMethodName = regexp.MustCompile(`^/([^/]+)/([^/.]+)$`) |
| 36 | ) |
| 37 | |
| 38 | // getMethodInfo returns the methodInfo for a given method name, as retrieved |
| 39 | // from grpc.{Stream,Unary}ServerInfo.FullMethod, or nil if the method could not |
| 40 | // be found. |
| 41 | // |
| 42 | // SECURITY: If the given method does not have any |
| 43 | // metropolis.common.ext.authorization annotations, a methodInfo which requires |
| 44 | // authorization but no permissions is returned, defaulting to a mildly secure |
| 45 | // default of a method that can be called by any authenticated user. |
| 46 | func getMethodInfo(methodName string) (*methodInfo, error) { |
| 47 | m := reMethodName.FindStringSubmatch(methodName) |
| 48 | if len(m) != 3 { |
| 49 | return nil, status.Errorf(codes.InvalidArgument, "invalid method name %q", methodName) |
| 50 | } |
| 51 | // Convert /foo.bar/Method to foo.bar.Method, which is used by the protoregistry. |
| 52 | methodName = fmt.Sprintf("%s.%s", m[1], m[2]) |
| 53 | desc, err := protoregistry.GlobalFiles.FindDescriptorByName(protoreflect.FullName(methodName)) |
| 54 | if err != nil { |
| 55 | return nil, status.Errorf(codes.InvalidArgument, "could not retrieve descriptor for method: %v", err) |
| 56 | } |
| 57 | method, ok := desc.(protoreflect.MethodDescriptor) |
| 58 | if !ok { |
| 59 | return nil, status.Error(codes.InvalidArgument, "querying method name did not yield a MethodDescriptor") |
| 60 | } |
| 61 | |
| 62 | // Get authorization extension, defaults to no options set. |
| 63 | if !proto.HasExtension(method.Options(), epb.E_Authorization) { |
| 64 | return nil, status.Errorf(codes.Internal, "method does not provide Authorization extension, failing safe") |
| 65 | } |
| 66 | authz, ok := proto.GetExtension(method.Options(), epb.E_Authorization).(*epb.Authorization) |
| 67 | if !ok { |
| 68 | return nil, status.Errorf(codes.Internal, "method contains Authorization extension with wrong type, failing safe") |
| 69 | } |
| 70 | if authz == nil { |
| 71 | return nil, status.Errorf(codes.Internal, "method contains nil Authorization extension, failing safe") |
| 72 | } |
| 73 | |
| 74 | // If unauthenticated connections are allowed, return immediately. |
| 75 | if authz.AllowUnauthenticated && len(authz.Need) == 0 { |
| 76 | return &methodInfo{ |
| 77 | unauthenticated: true, |
| 78 | }, nil |
| 79 | } |
| 80 | |
| 81 | // Otherwise, return needed permissions. |
| 82 | res := &methodInfo{ |
| 83 | need: make(map[epb.Permission]bool), |
| 84 | } |
| 85 | for _, n := range authz.Need { |
| 86 | res.need[n] = true |
| 87 | } |
| 88 | return res, nil |
| 89 | } |