go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/server/auth/grpc.go (about) 1 // Copyright 2023 The LUCI Authors. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package auth 16 17 import ( 18 "context" 19 "net/http" 20 "strings" 21 22 "google.golang.org/grpc/codes" 23 "google.golang.org/grpc/metadata" 24 "google.golang.org/grpc/peer" 25 "google.golang.org/grpc/status" 26 27 "go.chromium.org/luci/common/retry/transient" 28 "go.chromium.org/luci/grpc/grpcutil" 29 ) 30 31 type grpcRequestMetadata struct { 32 ctx context.Context 33 } 34 35 func (m grpcRequestMetadata) Header(key string) string { 36 if v := metadata.ValueFromIncomingContext(m.ctx, strings.ToLower(key)); len(v) != 0 { 37 return v[0] 38 } 39 return "" 40 } 41 42 func (m grpcRequestMetadata) Cookie(key string) (*http.Cookie, error) { 43 // Note: this is really used only when the transport is pRPC and requests come 44 // from a browser. 45 cookies := metadata.ValueFromIncomingContext(m.ctx, "cookie") 46 if len(cookies) == 0 { 47 return nil, http.ErrNoCookie 48 } 49 return (&http.Request{Header: http.Header{"Cookie": cookies}}).Cookie(key) 50 } 51 52 func (m grpcRequestMetadata) RemoteAddr() string { 53 if peer, ok := peer.FromContext(m.ctx); ok { 54 return peer.Addr.String() 55 } 56 return "" 57 } 58 59 func (m grpcRequestMetadata) Host() string { 60 if v := metadata.ValueFromIncomingContext(m.ctx, ":authority"); len(v) != 0 { 61 return v[0] 62 } 63 return "" 64 } 65 66 // RequestMetadataForGRPC returns a RequestMetadata of the current grpc request. 67 func RequestMetadataForGRPC(ctx context.Context) RequestMetadata { 68 return grpcRequestMetadata{ctx} 69 } 70 71 // AuthenticatingInterceptor authenticates incoming requests. 72 // 73 // It performs per-RPC authentication, i.e. it is a server counterpart of the 74 // client side PerRPCCredentials option. It doesn't do any transport-level 75 // authentication. 76 // 77 // It receives a list of Method implementations which will be applied one after 78 // another to try to authenticate the request until the first successful hit. If 79 // all methods end up to be non-applicable (i.e. none of the methods notice any 80 // metadata they recognize), the request will be passed through to the handler 81 // as anonymous (coming from an "anonymous identity"). Rejecting anonymous 82 // requests (if necessary) is the job of an authorization layer, often 83 // implemented as a separate gRPC interceptor. For simple cases use 84 // go.chromium.org/luci/server/auth/rpcacl interceptor. 85 // 86 // Additionally this interceptor adds an authentication state into the request 87 // context. It is used by various functions in this package such as 88 // CurrentIdentity and HasPermission. 89 // 90 // The context in the incoming request should be derived from a context that 91 // holds at least the auth library configuration (see Config and ModifyConfig), 92 // but ideally also other foundational things (like logging, monitoring, etc). 93 // This is usually already the case when running in a LUCI Server environment. 94 // 95 // May abort the request without calling the handler if the authentication 96 // process itself fails in some way. In particular: 97 // - PERMISSION_DENIED: a forbidden client IP or a token audience. 98 // - UNAUTHENTICATED: present, but totally malformed authorization metadata. 99 // - INTERNAL: some transient error. 100 func AuthenticatingInterceptor(methods []Method) grpcutil.UnifiedServerInterceptor { 101 au := Authenticator{Methods: methods} 102 return func(ctx context.Context, fullMethod string, handler func(ctx context.Context) error) error { 103 ctx, err := au.Authenticate(ctx, RequestMetadataForGRPC(ctx)) 104 if err != nil { 105 code, ok := grpcutil.Tag.In(err) 106 if !ok { 107 if transient.Tag.In(err) { 108 code = codes.Internal 109 } else { 110 code = codes.Unauthenticated 111 } 112 } 113 return status.Error(code, err.Error()) 114 } 115 return handler(ctx) 116 } 117 }