blob: baac5fcaa00b03a0fe6429fea65966b571fb5684 [file] [log] [blame]
// Copyright 2023 The LUCI Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package auth
import (
"context"
"net/http"
"strings"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/metadata"
"google.golang.org/grpc/peer"
"google.golang.org/grpc/status"
"go.chromium.org/luci/common/retry/transient"
"go.chromium.org/luci/grpc/grpcutil"
)
type grpcRequestMetadata struct {
ctx context.Context
}
func (m grpcRequestMetadata) Header(key string) string {
if v := metadata.ValueFromIncomingContext(m.ctx, strings.ToLower(key)); len(v) != 0 {
return v[0]
}
return ""
}
func (m grpcRequestMetadata) Cookie(key string) (*http.Cookie, error) {
// Note: this is really used only when the transport is pRPC and requests come
// from a browser.
cookies := metadata.ValueFromIncomingContext(m.ctx, "cookie")
if len(cookies) == 0 {
return nil, http.ErrNoCookie
}
return (&http.Request{Header: http.Header{"Cookie": cookies}}).Cookie(key)
}
func (m grpcRequestMetadata) RemoteAddr() string {
if peer, ok := peer.FromContext(m.ctx); ok {
return peer.Addr.String()
}
return ""
}
func (m grpcRequestMetadata) Host() string {
if v := metadata.ValueFromIncomingContext(m.ctx, ":authority"); len(v) != 0 {
return v[0]
}
return ""
}
// RequestMetadataForGRPC returns a RequestMetadata of the current grpc request.
func RequestMetadataForGRPC(ctx context.Context) RequestMetadata {
return grpcRequestMetadata{ctx}
}
// AuthenticatingInterceptor authenticates incoming requests.
//
// It performs per-RPC authentication, i.e. it is a server counterpart of the
// client side PerRPCCredentials option. It doesn't do any transport-level
// authentication.
//
// It receives a list of Method implementations which will be applied one after
// another to try to authenticate the request until the first successful hit. If
// all methods end up to be non-applicable (i.e. none of the methods notice any
// metadata they recognize), the request will be passed through to the handler
// as anonymous (coming from an "anonymous identity"). Rejecting anonymous
// requests (if necessary) is the job of an authorization layer, often
// implemented as a separate gRPC interceptor. For simple cases use
// go.chromium.org/luci/server/auth/rpcacl interceptor.
//
// Additionally this interceptor adds an authentication state into the request
// context. It is used by various functions in this package such as
// CurrentIdentity and HasPermission.
//
// The context in the incoming request should be derived from a context that
// holds at least the auth library configuration (see Config and ModifyConfig),
// but ideally also other foundational things (like logging, monitoring, etc).
// This is usually already the case when running in a LUCI Server environment.
//
// May abort the request without calling the handler if the authentication
// process itself fails in some way. In particular:
// - PERMISSION_DENIED: a forbidden client IP or a token audience.
// - UNAUTHENTICATED: present, but totally malformed authorization metadata.
// - INTERNAL: some transient error.
func AuthenticatingInterceptor(methods []Method) grpcutil.UnifiedServerInterceptor {
au := Authenticator{Methods: methods}
return func(ctx context.Context, fullMethod string, handler func(ctx context.Context) error) error {
ctx, err := au.Authenticate(ctx, RequestMetadataForGRPC(ctx))
if err != nil {
code, ok := grpcutil.Tag.In(err)
if !ok {
if transient.Tag.In(err) {
code = codes.Internal
} else {
code = codes.Unauthenticated
}
}
return status.Error(code, err.Error())
}
return handler(ctx)
}
}