diff --git a/pkg/account/client.go b/pkg/account/client.go index 7604f1da49b21e25a1eca3a9a9f0bc7307547a54..dcc46e282142dcdb5530f9d9b3898ac5ed885daa 100644 --- a/pkg/account/client.go +++ b/pkg/account/client.go @@ -1,11 +1,6 @@ package account import ( - "context" - "crypto/tls" - "crypto/x509" - "fmt" - "net/url" "time" "git.perx.ru/perxis/perxis-go/pkg/cache" @@ -16,13 +11,8 @@ import ( organizationsTransport "git.perx.ru/perxis/perxis-go/pkg/organizations/transport/grpc" serviceUsers "git.perx.ru/perxis/perxis-go/pkg/users/middleware" usersTransport "git.perx.ru/perxis/perxis-go/pkg/users/transport/grpc" - "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc" "go.uber.org/zap" - "golang.org/x/oauth2/clientcredentials" "google.golang.org/grpc" - "google.golang.org/grpc/credentials" - "google.golang.org/grpc/credentials/insecure" - "google.golang.org/grpc/credentials/oauth" ) const ( @@ -30,91 +20,34 @@ const ( DefaultCacheTTL = time.Second * 10 ) -func NewClient(ctx context.Context, addr string, opts ...Option) (*Account, *grpc.ClientConn, error) { +func NewClient(conn *grpc.ClientConn, opts ...Option) (*Account, error) { client := &Account{} - dialOpts := make([]grpc.DialOption, 0) - config := &config{} + c := &config{} for _, o := range opts { - o(config) + o(c) } - if config.logger == nil { - config.logger = zap.NewNop() + if c.logger == nil { + c.logger = zap.NewNop() } - dialOpts = append(dialOpts, grpc.WithUnaryInterceptor(otelgrpc.UnaryClientInterceptor()), - grpc.WithStreamInterceptor(otelgrpc.StreamClientInterceptor())) - - if config.auth == nil { - dialOpts = append(dialOpts, grpc.WithTransportCredentials(insecure.NewCredentials())) - } else { - if config.auth.TLS != nil { - - certPool := x509.NewCertPool() - - if !certPool.AppendCertsFromPEM(config.auth.TLS.cacert) { - return nil, nil, fmt.Errorf("CA certificate not loaded") - } - - clientCert, err := tls.X509KeyPair(config.auth.TLS.cert, config.auth.TLS.key) - if err != nil { - return nil, nil, err - - } - - tlsConfig := &tls.Config{ - Certificates: []tls.Certificate{clientCert}, - RootCAs: certPool, - } - - dialOpts = append(dialOpts, - grpc.WithTransportCredentials(credentials.NewTLS(tlsConfig)), - ) - } - - if config.auth.OAuth2 != nil { - if config.auth.OAuth2 != nil { - // create external grpc client - conf := &clientcredentials.Config{ - TokenURL: config.auth.OAuth2.tokenURL, - ClientID: config.auth.OAuth2.clientID, - ClientSecret: config.auth.OAuth2.clientSecret, - EndpointParams: url.Values{"audience": {config.auth.OAuth2.audience}}, - } - cred := oauth.TokenSource{ - TokenSource: conf.TokenSource(ctx), - } - dialOpts = append(dialOpts, - grpc.WithPerRPCCredentials(cred), - grpc.WithTransportCredentials(credentials.NewTLS(&tls.Config{InsecureSkipVerify: true})), // обязательно использовать tls для credentials oauth.TokenSource https://github.com/grpc/grpc-go/blob/64031cbfcf4d84c026be93ad7b74b3c290100893/credentials/oauth/oauth.go#L160 - ) - } - } + client.Members = membersTransport.NewGRPCClient(conn, "", c.clientOptions...) + client.Organizations = organizationsTransport.NewGRPCClient(conn, "", c.clientOptions...) + client.Users = usersTransport.NewGRPCClient(conn, "", c.clientOptions...) + client.MembersObserver = membersObserverTransport.NewGRPCClient(conn, "", c.clientOptions...) - } - - accountConn, err := grpc.Dial(addr, dialOpts...) - if err != nil { - return nil, nil, err - } - - client.Members = membersTransport.NewGRPCClient(accountConn, "", config.clientOptions...) - client.Organizations = organizationsTransport.NewGRPCClient(accountConn, "", config.clientOptions...) - client.Users = usersTransport.NewGRPCClient(accountConn, "", config.clientOptions...) - client.MembersObserver = membersObserverTransport.NewGRPCClient(accountConn, "", config.clientOptions...) - - if !config.noCache { + if !c.noCache { client = WithCaching(client, DefaultCacheSize, DefaultCacheTTL) } - if !config.noLog { - client = WithLogging(client, config.logger, config.accessLog) + if !c.noLog { + client = WithLogging(client, c.logger, c.accessLog) } - return client, accountConn, nil + return client, nil } func WithCaching(client *Account, size int, ttl time.Duration) *Account { diff --git a/pkg/account/config.go b/pkg/account/config.go index ca7218b594ace4315ef1cae578616d4eaadea7b4..cfa3f088e56829ad3917e274f6e59938fa0098d7 100644 --- a/pkg/account/config.go +++ b/pkg/account/config.go @@ -1,69 +1,21 @@ package account import ( - "github.com/go-kit/kit/transport/grpc" + kitgrpc "github.com/go-kit/kit/transport/grpc" "go.uber.org/zap" ) type config struct { - auth *authConfig - noCache bool - noLog bool - accessLog bool - debug bool - - clientOptions []grpc.ClientOption - - logger *zap.Logger -} - -type authConfig struct { - OAuth2 *authOAuth2Config - TLS *authTLSConfig -} - -type authOAuth2Config struct { - tokenURL string - clientID string // параметр из auth0 (клиент с таким id должен быть создан в perxis) - clientSecret string - audience string // параметр из auth0 (название связанного с Application API) -} - -type authTLSConfig struct { - cacert []byte - cert []byte - key []byte + noCache bool + noLog bool + accessLog bool + debug bool + clientOptions []kitgrpc.ClientOption // todo: можно заменить на grpc-интерсепторы при соединении и избавиться здесь от go-kit + logger *zap.Logger } type Option func(c *config) -func AuthOAuth2(tokenUrl, clientID, clientSecret, audience string) Option { - return func(c *config) { - if c.auth == nil { - c.auth = &authConfig{} - } - c.auth.OAuth2 = &authOAuth2Config{ - tokenURL: tokenUrl, - clientID: clientID, - clientSecret: clientSecret, - audience: audience, - } - } -} - -func AuthTLS(cacert, cert, key []byte) Option { - return func(c *config) { - if c.auth == nil { - c.auth = &authConfig{} - } - c.auth.TLS = &authTLSConfig{ - cacert: cacert, - cert: cert, - key: key, - } - } -} - func NoCache() Option { return func(c *config) { c.noCache = true @@ -76,7 +28,7 @@ func NoLog() Option { } } -func GrpcClientOptions(opts ...grpc.ClientOption) Option { +func GrpcClientOptions(opts ...kitgrpc.ClientOption) Option { return func(c *config) { c.clientOptions = opts } diff --git a/pkg/auth/grpc.go b/pkg/auth/grpc.go index 7a566711db76c11b22206d947ba94ecc2bc3b366..92399d0ccfbe1540d2db32ee1345662960f97eaf 100644 --- a/pkg/auth/grpc.go +++ b/pkg/auth/grpc.go @@ -2,9 +2,16 @@ package auth import ( "context" + "crypto/tls" + "crypto/x509" + "net/url" + "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" ) @@ -90,3 +97,35 @@ func PrincipalClientInterceptor() grpc.UnaryClientInterceptor { 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...) + } +} + +// 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) { + 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 grpc.WithTransportCredentials(credentials.NewTLS(&tls.Config{Certificates: []tls.Certificate{clientCert}, RootCAs: certPool})), nil +} diff --git a/pkg/content/client.go b/pkg/content/client.go index a67648427576b7a9134cd4fdf3715273a5c56ac3..af5ba5bf9956cfaa51e0636acff0d98c82edfb38 100644 --- a/pkg/content/client.go +++ b/pkg/content/client.go @@ -1,12 +1,6 @@ package content import ( - "context" - "crypto/tls" - "crypto/x509" - "errors" - "fmt" - "net/url" "time" "git.perx.ru/perxis/perxis-go/pkg/cache" @@ -31,12 +25,7 @@ import ( spacesSvc "git.perx.ru/perxis/perxis-go/pkg/spaces/middleware" spacesTransportGrpc "git.perx.ru/perxis/perxis-go/pkg/spaces/transport/grpc" "go.uber.org/zap" - "golang.org/x/oauth2/clientcredentials" "google.golang.org/grpc" - "google.golang.org/grpc/credentials" - "google.golang.org/grpc/credentials/insecure" - "google.golang.org/grpc/credentials/oauth" - "google.golang.org/grpc/metadata" ) const ( @@ -44,11 +33,9 @@ const ( DefaultCacheTTL = time.Second * 10 ) -func NewClient(addr string, opts ...Option) (*Content, *grpc.ClientConn, error) { - ctx := context.Background() +func NewClient(conn *grpc.ClientConn, opts ...Option) *Content { client := &Content{} - dialOpts := make([]grpc.DialOption, 0) config := &Config{} for _, o := range opts { @@ -59,27 +46,16 @@ func NewClient(addr string, opts ...Option) (*Content, *grpc.ClientConn, error) config.Logger = zap.NewNop() } - authDialOpts, err := config.GetAuthDialOpts(ctx) - if err != nil { - return nil, nil, err - } - dialOpts = append(dialOpts, authDialOpts...) - - contentConn, err := grpc.Dial(addr, dialOpts...) - if err != nil { - return nil, nil, err - } - - client.Spaces = spacesTransportGrpc.NewClient(contentConn, config.ClientOptions...) - client.Environments = environmentsTransportGrpc.NewGRPCClient(contentConn, "", config.ClientOptions...) - client.Collections = collectionsTransportGrpc.NewGRPCClient(contentConn, "", config.ClientOptions...) - client.Items = itemsTransportGrpc.NewClient(contentConn, config.ClientOptions...) - client.Invitations = invitationsTransportGrpc.NewGRPCClient(contentConn, "", config.ClientOptions...) - client.Collaborators = collaboratorsTransportGrpc.NewGRPCClient(contentConn, "", config.ClientOptions...) - client.Clients = clientsTransportGrpc.NewGRPCClient(contentConn, "", config.ClientOptions...) - client.Locales = localsTransportGrpc.NewGRPCClient(contentConn, "", config.ClientOptions...) - client.Roles = rolesTransportGrpc.NewGRPCClient(contentConn, "", config.ClientOptions...) - client.References = referencesTransportGrpc.NewGRPCClient(contentConn, "", config.ClientOptions...) + client.Spaces = spacesTransportGrpc.NewClient(conn, config.ClientOptions...) + client.Environments = environmentsTransportGrpc.NewGRPCClient(conn, "", config.ClientOptions...) + client.Collections = collectionsTransportGrpc.NewGRPCClient(conn, "", config.ClientOptions...) + client.Items = itemsTransportGrpc.NewClient(conn, config.ClientOptions...) + client.Invitations = invitationsTransportGrpc.NewGRPCClient(conn, "", config.ClientOptions...) + client.Collaborators = collaboratorsTransportGrpc.NewGRPCClient(conn, "", config.ClientOptions...) + client.Clients = clientsTransportGrpc.NewGRPCClient(conn, "", config.ClientOptions...) + client.Locales = localsTransportGrpc.NewGRPCClient(conn, "", config.ClientOptions...) + client.Roles = rolesTransportGrpc.NewGRPCClient(conn, "", config.ClientOptions...) + client.References = referencesTransportGrpc.NewGRPCClient(conn, "", config.ClientOptions...) if !config.NoDecode { client.Items = itemsSvc.ClientEncodeMiddleware(client.Collections)(client.Items) @@ -94,7 +70,7 @@ func NewClient(addr string, opts ...Option) (*Content, *grpc.ClientConn, error) client = WithLogging(client, config.Logger, config.AccessLog) } - return client, contentConn, nil + return client } func WithCaching(client *Content, size int, ttl time.Duration) *Content { @@ -129,79 +105,3 @@ func WithLogging(cs *Content, logger *zap.Logger, accessLog bool) *Content { return &s } - -func AddAPIKeyInterceptor(key 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", "API-Key"+" "+key) - return invoker(ctx, method, req, reply, cc, opts...) - } -} - -func (c *Config) GetAuthDialOpts(ctx context.Context) (opts []grpc.DialOption, err error) { - - if c.Auth == nil { - opts = append(opts, grpc.WithTransportCredentials(insecure.NewCredentials())) - return - } - - authConfig := c.Auth - - switch { - case authConfig.OAuth2 != nil: - // create external grpc client - conf := &clientcredentials.Config{ - TokenURL: authConfig.OAuth2.TokenURL, - ClientID: authConfig.OAuth2.ClientID, - ClientSecret: authConfig.OAuth2.ClientSecret, - EndpointParams: url.Values{"audience": {authConfig.OAuth2.Audience}}, - } - - // обязательно использовать tls для credentials oauth.TokenSource https://github.com/grpc/grpc-go/blob/64031cbfcf4d84c026be93ad7b74b3c290100893/credentials/oauth/oauth.go#L160 - if authConfig.Insecure { - return nil, errors.New("oauth requires tls") - } - if authConfig.TLS == nil { - authConfig.TLS = &TLS{SkipVerify: true} - } - - opts = append(opts, - grpc.WithPerRPCCredentials(oauth.TokenSource{ - TokenSource: conf.TokenSource(ctx), - }), - ) - case authConfig.APIKey != nil: - - if !authConfig.Insecure && authConfig.TLS == nil { - authConfig.TLS = &TLS{SkipVerify: true} - } - opts = append(opts, - grpc.WithUnaryInterceptor(AddAPIKeyInterceptor(authConfig.APIKey.APIKey)), - ) - } - - switch { - case authConfig.TLS != nil && !authConfig.SkipVerify: - certPool := x509.NewCertPool() - if !certPool.AppendCertsFromPEM(authConfig.TLS.CaCert) { - return nil, fmt.Errorf("CA certificate not loaded") - } - - clientCert, err := tls.X509KeyPair(authConfig.TLS.Cert, authConfig.TLS.Key) - if err != nil { - return nil, err - } - - opts = append(opts, - grpc.WithTransportCredentials(credentials.NewTLS(&tls.Config{ - Certificates: []tls.Certificate{clientCert}, - RootCAs: certPool, - })), - ) - case authConfig.TLS != nil && authConfig.SkipVerify: - opts = append(opts, grpc.WithTransportCredentials(credentials.NewTLS(&tls.Config{InsecureSkipVerify: true}))) - default: - opts = append(opts, grpc.WithTransportCredentials(insecure.NewCredentials())) - } - - return -} diff --git a/pkg/content/config.go b/pkg/content/config.go index b77d7ddb68be20402c5b2d5807c58c325a9a4f2e..37c6cc537edadcb29f06195963cc7d71d4f38c49 100644 --- a/pkg/content/config.go +++ b/pkg/content/config.go @@ -6,7 +6,6 @@ import ( ) type Config struct { - Auth *AuthConfig NoCache bool NoLog bool AccessLog bool @@ -14,88 +13,11 @@ type Config struct { NoDecode bool ClientOptions []kitgrpc.ClientOption - - Logger *zap.Logger -} - -type AuthConfig struct { - *OAuth2 `json:"oauth_2,omitempty"` - *APIKey `json:"api_key,omitempty"` - *TLS `json:"tls,omitempty"` - Insecure bool `json:"insecure,omitempty"` -} - -type OAuth2 struct { - TokenURL string `json:"token_url,omitempty"` - ClientID string `json:"client_id,omitempty"` // параметр из auth0 (клиент с таким id должен быть создан в perxis) - ClientSecret string `json:"client_secret,omitempty"` - Audience string `json:"audience,omitempty"` // параметр из auth0 (название связанного с Application API) -} - -type TLS struct { - CaCert []byte `json:"tls-cacert"` - Cert []byte `json:"tls-cert"` - Key []byte `json:"tls-key"` - SkipVerify bool -} - -type APIKey struct { - APIKey string `json:"api_key"` + Logger *zap.Logger } type Option func(c *Config) -func AuthOAuth2(tokenUrl, clientID, clientSecret, audience string) Option { - return func(c *Config) { - if c.Auth == nil { - c.Auth = &AuthConfig{} - } - c.Auth.OAuth2 = &OAuth2{ - TokenURL: tokenUrl, - ClientID: clientID, - ClientSecret: clientSecret, - Audience: audience, - } - } -} - -func AuthTLS(cacert, cert, key []byte) Option { - return func(c *Config) { - if c.Auth == nil { - c.Auth = &AuthConfig{} - } - c.Auth.TLS = &TLS{ - CaCert: cacert, - Cert: cert, - Key: key, - } - } -} - -func AuthAPIKey(key string) Option { - return func(c *Config) { - if c.Auth == nil { - c.Auth = &AuthConfig{} - } - c.Auth.APIKey = &APIKey{APIKey: key} - } -} - -func AuthInsecure() Option { - return func(c *Config) { - if c.Auth == nil { - c.Auth = &AuthConfig{} - } - c.Auth.Insecure = true - } -} - -func Auth(cfg *AuthConfig) Option { - return func(c *Config) { - c.Auth = cfg - } -} - func NoCache() Option { return func(c *Config) { c.NoCache = true diff --git a/pkg/delivery/client/client.go b/pkg/delivery/client/client.go index 4f7d9a97ab4a978f3fd6c5708f6b5691067b64c0..1de0dd684a2344264ad217c2834fbb74f19d837f 100644 --- a/pkg/delivery/client/client.go +++ b/pkg/delivery/client/client.go @@ -1,7 +1,6 @@ package client import ( - "context" "time" "git.perx.ru/perxis/perxis-go/pkg/cache" @@ -15,26 +14,13 @@ import ( "google.golang.org/grpc" ) -func NewClient(addr string, opts ...Option) (delivery.Delivery, error) { - ctx := context.Background() +func NewClient(conn *grpc.ClientConn, opts ...Option) (delivery.Delivery, error) { c := new(Config) - dialOpts := make([]grpc.DialOption, 0) - for _, o := range opts { o(&c.Config) } - authDialOpts, err := c.GetAuthDialOpts(ctx) - if err != nil { - return nil, err - } - - conn, err := grpc.Dial(addr, append(dialOpts, authDialOpts...)...) - if err != nil { - return nil, err - } - client := deliverytransportgrpc.NewGRPCClient(conn, "", c.ClientOptions...) cfg := &deliveryservice.Config{ diff --git a/pkg/delivery/client/config.go b/pkg/delivery/client/config.go index 27f34b0bef42e7ad5aa40093a2f4188f62d1c556..ca1a65421a5a988a2171f32edd9e0abdf2ed0e2c 100644 --- a/pkg/delivery/client/config.go +++ b/pkg/delivery/client/config.go @@ -11,22 +11,6 @@ type Config struct { contentclient.Config } -func AuthOAuth2(tokenUrl, clientID, clientSecret, audience string) Option { - return Option(contentclient.AuthOAuth2(tokenUrl, clientID, clientSecret, audience)) -} - -func AuthTLS(cacert, cert, key []byte) Option { - return Option(contentclient.AuthTLS(cacert, cert, key)) -} - -func AuthAPIKey(key string) Option { - return Option(contentclient.AuthAPIKey(key)) -} - -func AuthInsecure() Option { - return Option(contentclient.AuthInsecure()) -} - func GrpcClientOptions(opts ...grpc.ClientOption) Option { return Option(contentclient.GrpcClientOptions(opts...)) }