Skip to content
Snippets Groups Projects
Select Git revision
  • 90c5e5e65f8fffdc914763e140e0a3166481d43d
  • master default protected
  • feature/PRXS-3383-CollectionsSort
  • feature/2781-SpacesLoggingMiddleware
  • feature/PRXS-3421-ImplementNewRefAPI
  • feature/PRXS-3143-3235-ReferenceOptions
  • feature/PRXS-3143-LimitReferenceFields
  • feature/PRXS-3234-FeaturePruneIdents
  • PRXS-3421-RecursiveReferences
  • feature/3109-SerializeFeature
  • release/0.33
  • feature/3109-RecoverySchema
  • feature/3109-feature
  • fix/PRXS-3369-ValidateFields
  • refactor/PRXS-3306-MovePkgGroup1
  • refactor/6-pkg-refactor-expr
  • fix/PRXS-3360-TemplateBuilderPatch
  • feature/3293-MongoV2
  • feature/3272-GoVersionUp
  • feature/PRXS-3218-HideTemplateActions
  • feature/PRXS-3234-PruneIdents
  • v0.33.1
  • v0.32.0
  • v0.31.1
  • v0.31.0
  • v0.30.0
  • v0.29.0
  • v0.28.0
  • v0.27.0-alpha.1+16
  • v0.27.0-alpha.1+15
  • v0.27.0-alpha.1+14
  • v0.27.0-alpha.1+13
  • v0.27.0-alpha.1+12
  • v0.27.0-alpha.1+11
  • v0.27.0-alpha.1+10
  • v0.27.0-alpha.1+9
  • v0.27.0-alpha.1+8
  • v0.27.0-alpha.1+7
  • v0.27.0-alpha.1+6
  • v0.27.0-alpha.1+5
  • v0.27.0-alpha.1+4
41 results

grpc.go

Blame
  • grpc.go 8.26 KiB
    package auth
    
    import (
    	"context"
    	"crypto/tls"
    	"crypto/x509"
    	"net/url"
    
    	"connectrpc.com/connect"
    	"git.perx.ru/perxis/perxis-go/pkg/errors"
    	kitgrpc "github.com/go-kit/kit/transport/grpc"
    	"golang.org/x/oauth2/clientcredentials"
    	"google.golang.org/grpc"
    	"google.golang.org/grpc/credentials"
    	"google.golang.org/grpc/credentials/oauth"
    	"google.golang.org/grpc/metadata"
    )
    
    const (
    	OAuth2IdentityMetadata = "x-perxis-identity"
    	TLSIdentityMetadata    = "x-forwarded-client-cert"
    	AccessMetadata         = "x-perxis-access"
    
    	AuthorizationMetadata = "authorization"
    )
    
    func GRPCToContext(factory *PrincipalFactory) kitgrpc.ServerRequestFunc {
    	return func(ctx context.Context, md metadata.MD) context.Context {
    		if identity := md.Get(TLSIdentityMetadata); len(identity) > 0 {
    			return WithPrincipal(ctx, factory.Principal(identity[0]))
    		}
    
    		if identity := md.Get(OAuth2IdentityMetadata); len(identity) > 0 {
    			return WithPrincipal(ctx, factory.Principal(identity[0]))
    		}
    
    		if identity := md.Get(AuthorizationMetadata); len(identity) > 0 {
    			return WithPrincipal(ctx, factory.Principal(identity[0]))
    		}
    
    		if access := md.Get(AccessMetadata); len(access) > 0 {
    			return WithPrincipal(ctx, factory.System())
    		}
    
    		return WithPrincipal(ctx, factory.Anonymous())
    	}
    }
    
    func ContextToGRPC() kitgrpc.ClientRequestFunc {
    	return func(ctx context.Context, md *metadata.MD) context.Context {
    		p := GetPrincipal(ctx)
    
    		switch p := p.(type) {
    		case *UserPrincipal:
    			if p.GetIdentity(ctx) != "" {
    				(*md)[OAuth2IdentityMetadata] = []string{p.GetIdentity(ctx)}
    			}
    		case *ClientPrincipal:
    			if ident := p.GetIdentity(ctx); ident != nil {
    				switch {
    				case ident.OAuthClientID != "":
    					(*md)[OAuth2IdentityMetadata] = []string{ident.OAuthClientID + "@clients"}
    				case ident.TLSSubject != "":
    					(*md)[TLSIdentityMetadata] = []string{ident.TLSSubject}
    				case ident.APIKey != "":
    					(*md)[AuthorizationMetadata] = []string{"API-Key " + ident.APIKey}
    
    				}
    			}
    		case *SystemPrincipal:
    			(*md)[AccessMetadata] = []string{p.GetID(ctx)}
    		}
    
    		return ctx
    	}
    }
    
    // PrincipalServerInterceptor - grpc-интерсептор, который используется для получения данных принципала из grpc-метаданы и добавления в контекст ”. В случае, если
    // сервис не использует проверку прав 'Principal' к системе, в параметрах передается пустой объект '&PrincipalFactory{}'
    func PrincipalServerInterceptor(factory *PrincipalFactory) grpc.UnaryServerInterceptor {
    	return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
    		if md, ok := metadata.FromIncomingContext(ctx); ok {
    			ctx = GRPCToContext(factory)(ctx, md)
    		}
    		return handler(ctx, req)
    	}
    }
    
    // PrincipalClientInterceptor - grpc-интерсептор, который используется для получения данных принципала. В случае, если
    // сервис не использует проверку прав 'Principal' к системе, в параметрах передается пустой объект '&PrincipalFactory{}'
    func PrincipalClientInterceptor() grpc.UnaryClientInterceptor {
    	return func(ctx context.Context, method string, req, reply interface{}, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error {
    		md, ok := metadata.FromOutgoingContext(ctx)
    		if !ok {
    			md = metadata.MD{}
    		}
    		ctx = metadata.NewOutgoingContext(ContextToGRPC()(ctx, &md), md)
    		return invoker(ctx, method, req, reply, cc, opts...)
    	}
    }
    
    func AddAccessInterceptor(id string) grpc.UnaryClientInterceptor {
    	return func(ctx context.Context, method string, req, reply interface{}, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error {
    		ctx = metadata.AppendToOutgoingContext(ctx, AccessMetadata, id)
    		return invoker(ctx, method, req, reply, cc, opts...)
    	}
    }
    
    func AddAuthorizationInterceptor(auth string) grpc.UnaryClientInterceptor {
    	return func(ctx context.Context, method string, req, reply interface{}, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error {
    		ctx = metadata.AppendToOutgoingContext(ctx, "authorization", auth)
    		return invoker(ctx, method, req, reply, cc, opts...)
    	}
    }
    
    // AddAPIKeyInterceptor возвращает опции для создания grpc-соединения с передачей API-ключа при каждом запросе
    func AddAPIKeyInterceptor(key string) grpc.UnaryClientInterceptor {
    	return AddAuthorizationInterceptor("API-Key " + key)
    }
    
    // WithOAuth2Credentials возвращает опции для создания grpc-соединения с аутентификацией oauth2. Переданный контекст
    // будет использован для каждого запроса токена, поэтому он не может быть с таймаутом.
    func WithOAuth2Credentials(ctx context.Context, tokenURL, clientID, clientSecret, audience string) grpc.DialOption {
    	conf := &clientcredentials.Config{
    		TokenURL:       tokenURL,
    		ClientID:       clientID,
    		ClientSecret:   clientSecret,
    		EndpointParams: url.Values{"audience": {audience}},
    	}
    	return grpc.WithPerRPCCredentials(oauth.TokenSource{TokenSource: conf.TokenSource(ctx)})
    }
    
    // WithTLSCredentials возвращает опции для создания grpc-соединения с TLS-сертификатами
    func WithTLSCredentials(ctx context.Context, cert, cacert, key []byte) (grpc.DialOption, error) {
    	creds, err := TLSCredentials(ctx, cert, cacert, key)
    	if err != nil {
    		return nil, err
    	}
    	return grpc.WithTransportCredentials(creds), nil
    }
    
    // TLSCredentials возвращает TransportCredentials для создания grpc-соединения с TLS-сертификатами
    func TLSCredentials(ctx context.Context, cert, cacert, key []byte) (credentials.TransportCredentials, error) {
    	certPool := x509.NewCertPool()
    	if !certPool.AppendCertsFromPEM(cacert) {
    		return nil, errors.New("CA certificate not loaded")
    	}
    	clientCert, err := tls.X509KeyPair(cert, key)
    	if err != nil {
    		return nil, err
    	}
    	return credentials.NewTLS(&tls.Config{Certificates: []tls.Certificate{clientCert}, RootCAs: certPool}), nil
    }
    
    // PrincipalInterceptorConnect интерсептор для клиента и сервера
    // используется для получения данных принципала из запроса и добавления в контекст.
    func PrincipalInterceptorConnect(factory *PrincipalFactory) connect.UnaryInterceptorFunc { //nolint:gocognit // example
    	interceptor := func(next connect.UnaryFunc) connect.UnaryFunc {
    		return func(
    			ctx context.Context,
    			req connect.AnyRequest,
    		) (connect.AnyResponse, error) {
    			if req.Spec().IsClient {
    				p := GetPrincipal(ctx)
    				switch p := p.(type) {
    				case *UserPrincipal:
    					if p.GetIdentity(ctx) != "" {
    						req.Header().Set(OAuth2IdentityMetadata, p.GetIdentity(ctx))
    					}
    				case *ClientPrincipal:
    					if ident := p.GetIdentity(ctx); ident != nil {
    						switch {
    						case ident.OAuthClientID != "":
    							req.Header().Set(OAuth2IdentityMetadata, ident.OAuthClientID+"@clients")
    						case ident.TLSSubject != "":
    							req.Header().Set(TLSIdentityMetadata, ident.TLSSubject)
    						case ident.APIKey != "":
    							req.Header().Set(AuthorizationMetadata, "API-Key "+ident.APIKey)
    						}
    					}
    				case *SystemPrincipal:
    					req.Header().Set(AccessMetadata, p.GetID(ctx))
    				}
    				return next(ctx, req)
    			}
    			if identity := req.Header().Get(TLSIdentityMetadata); identity != "" {
    				ctx = WithPrincipal(ctx, factory.Principal(identity))
    				return next(ctx, req)
    			}
    
    			if identity := req.Header().Get(OAuth2IdentityMetadata); identity != "" {
    				ctx = WithPrincipal(ctx, factory.Principal(identity))
    				return next(ctx, req)
    			}
    
    			if identity := req.Header().Get(AuthorizationMetadata); identity != "" {
    				ctx = WithPrincipal(ctx, factory.Principal(identity))
    				return next(ctx, req)
    			}
    
    			if access := req.Header().Get(AccessMetadata); access != "" {
    				ctx = WithPrincipal(ctx, factory.Principal(access))
    				return next(ctx, req)
    			}
    
    			ctx = WithPrincipal(ctx, factory.Anonymous())
    			return next(ctx, req)
    		}
    	}
    
    	return interceptor
    }