From b68d9f3396ac143834d10d1155409c65de3a02f0 Mon Sep 17 00:00:00 2001 From: Alena Petraki <alena.petraki@gmail.com> Date: Wed, 26 Apr 2023 11:09:53 +0300 Subject: [PATCH] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB=D0=B5?= =?UTF-8?q?=D0=BD=D1=8B=20=D0=BE=D0=BF=D1=86=D0=B8=D0=B8=20=D0=B4=D0=BB?= =?UTF-8?q?=D1=8F=20=D0=BA=D0=BE=D0=BD=D1=84=D0=B8=D0=B3=D1=83=D1=80=D0=B0?= =?UTF-8?q?=D1=86=D0=B8=D0=B8=20=D0=BA=D0=BB=D0=B8=D0=B5=D0=BD=D1=82=D0=BE?= =?UTF-8?q?=D0=B2=20Account,=20Content?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pkg/account/client.go | 136 +++++++++++++++++++++++------------------- pkg/account/config.go | 54 +++++++++++++---- pkg/content/client.go | 90 +--------------------------- pkg/content/config.go | 97 ++++++++++++++++++++++++++++++ pkg/transport/grpc.go | 15 +++++ pkg/transport/http.go | 35 +++++++++++ 6 files changed, 266 insertions(+), 161 deletions(-) create mode 100644 pkg/transport/grpc.go create mode 100644 pkg/transport/http.go diff --git a/pkg/account/client.go b/pkg/account/client.go index 7604f1da..b8127402 100644 --- a/pkg/account/client.go +++ b/pkg/account/client.go @@ -9,6 +9,7 @@ import ( "time" "git.perx.ru/perxis/perxis-go/pkg/cache" + "git.perx.ru/perxis/perxis-go/pkg/errors" serviceMembers "git.perx.ru/perxis/perxis-go/pkg/members/middleware" membersObserverTransport "git.perx.ru/perxis/perxis-go/pkg/members/observer/transport/grpc" membersTransport "git.perx.ru/perxis/perxis-go/pkg/members/transport/grpc" @@ -33,85 +34,41 @@ const ( func NewClient(ctx context.Context, addr string, opts ...Option) (*Account, *grpc.ClientConn, 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()), + c.dialOptions = append(c.dialOptions, 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 - ) - } - } - + authOptions, err := getAuthDialOpts(ctx, c) + if err != nil { + return nil, nil, err } - accountConn, err := grpc.Dial(addr, dialOpts...) + accountConn, err := grpc.Dial(addr, append(c.dialOptions, authOptions...)...) 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...) + client.Members = membersTransport.NewGRPCClient(accountConn, "", c.clientOptions...) + client.Organizations = organizationsTransport.NewGRPCClient(accountConn, "", c.clientOptions...) + client.Users = usersTransport.NewGRPCClient(accountConn, "", c.clientOptions...) + client.MembersObserver = membersObserverTransport.NewGRPCClient(accountConn, "", c.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 @@ -136,3 +93,62 @@ func WithLogging(client *Account, logger *zap.Logger, accessLog bool) *Account { return &c } + +func getAuthDialOpts(ctx context.Context, c *config) (opts []grpc.DialOption, err error) { + + if c.auth == nil { + opts = append(opts, grpc.WithTransportCredentials(insecure.NewCredentials())) + return + } + + if c.auth.oauth2 != nil { + // create external grpc client + conf := &clientcredentials.Config{ + TokenURL: c.auth.oauth2.tokenURL, + ClientID: c.auth.oauth2.clientID, + ClientSecret: c.auth.oauth2.clientSecret, + EndpointParams: url.Values{"audience": {c.auth.oauth2.audience}}, + } + + // обÑзательно иÑпользовать tls Ð´Ð»Ñ credentials oauth.TokenSource https://github.com/grpc/grpc-go/blob/64031cbfcf4d84c026be93ad7b74b3c290100893/credentials/oauth/oauth.go#L160 + if c.auth.insecure { + return nil, errors.New("oauth requires tls") + } + if c.auth.tls == nil { + c.auth.tls = &tlsConfig{skipVerify: true} + } + + opts = append(opts, grpc.WithPerRPCCredentials(oauth.TokenSource{TokenSource: conf.TokenSource(ctx)})) + } + + if c.auth.tls != nil { + + if c.auth.tls.skipVerify { + opts = append(opts, grpc.WithTransportCredentials(credentials.NewTLS(&tls.Config{InsecureSkipVerify: true}))) + + } else { + certPool := x509.NewCertPool() + if !certPool.AppendCertsFromPEM(c.auth.tls.cacert) { + return nil, fmt.Errorf("CA certificate not loaded") + } + + clientCert, err := tls.X509KeyPair(c.auth.tls.cert, c.auth.tls.key) + if err != nil { + return nil, err + } + + opts = append(opts, + grpc.WithTransportCredentials(credentials.NewTLS(&tls.Config{ + Certificates: []tls.Certificate{clientCert}, + RootCAs: certPool, + })), + ) + } + } + + if c.auth.insecure { + opts = append(opts, grpc.WithTransportCredentials(insecure.NewCredentials())) + } + + return +} diff --git a/pkg/account/config.go b/pkg/account/config.go index ca7218b5..fa213360 100644 --- a/pkg/account/config.go +++ b/pkg/account/config.go @@ -1,8 +1,9 @@ package account import ( - "github.com/go-kit/kit/transport/grpc" + kitgrpc "github.com/go-kit/kit/transport/grpc" "go.uber.org/zap" + "google.golang.org/grpc" ) type config struct { @@ -12,27 +13,30 @@ type config struct { accessLog bool debug bool - clientOptions []grpc.ClientOption + clientOptions []kitgrpc.ClientOption + dialOptions []grpc.DialOption logger *zap.Logger } type authConfig struct { - OAuth2 *authOAuth2Config - TLS *authTLSConfig + oauth2 *oauth2Config + tls *tlsConfig + insecure bool } -type authOAuth2Config struct { +type oauth2Config struct { tokenURL string clientID string // параметр из auth0 (клиент Ñ Ñ‚Ð°ÐºÐ¸Ð¼ id должен быть Ñоздан в perxis) clientSecret string audience string // параметр из auth0 (название ÑвÑзанного Ñ Application API) } -type authTLSConfig struct { - cacert []byte - cert []byte - key []byte +type tlsConfig struct { + cacert []byte + cert []byte + key []byte + skipVerify bool } type Option func(c *config) @@ -42,7 +46,7 @@ func AuthOAuth2(tokenUrl, clientID, clientSecret, audience string) Option { if c.auth == nil { c.auth = &authConfig{} } - c.auth.OAuth2 = &authOAuth2Config{ + c.auth.oauth2 = &oauth2Config{ tokenURL: tokenUrl, clientID: clientID, clientSecret: clientSecret, @@ -56,7 +60,7 @@ func AuthTLS(cacert, cert, key []byte) Option { if c.auth == nil { c.auth = &authConfig{} } - c.auth.TLS = &authTLSConfig{ + c.auth.tls = &tlsConfig{ cacert: cacert, cert: cert, key: key, @@ -64,6 +68,26 @@ func AuthTLS(cacert, cert, key []byte) Option { } } +func AuthInsecure() Option { + return func(c *config) { + if c.auth == nil { + c.auth = &authConfig{} + } + c.auth.insecure = true + } +} + +func AuthTLSSkipVerify() Option { + return func(c *config) { + if c.auth == nil { + c.auth = &authConfig{} + } + c.auth.tls = &tlsConfig{ + skipVerify: true, + } + } +} + func NoCache() Option { return func(c *config) { c.noCache = true @@ -76,12 +100,18 @@ func NoLog() Option { } } -func GrpcClientOptions(opts ...grpc.ClientOption) Option { +func GrpcClientOptions(opts ...kitgrpc.ClientOption) Option { return func(c *config) { c.clientOptions = opts } } +func GrpcDialOptionsOptions(opts ...grpc.DialOption) Option { + return func(c *config) { + c.dialOptions = opts + } +} + func Logger(logger *zap.Logger) Option { return func(c *config) { c.logger = logger diff --git a/pkg/content/client.go b/pkg/content/client.go index a6764842..8e642719 100644 --- a/pkg/content/client.go +++ b/pkg/content/client.go @@ -2,11 +2,6 @@ package content import ( "context" - "crypto/tls" - "crypto/x509" - "errors" - "fmt" - "net/url" "time" "git.perx.ru/perxis/perxis-go/pkg/cache" @@ -31,12 +26,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 ( @@ -48,7 +38,6 @@ func NewClient(addr string, opts ...Option) (*Content, *grpc.ClientConn, error) ctx := context.Background() client := &Content{} - dialOpts := make([]grpc.DialOption, 0) config := &Config{} for _, o := range opts { @@ -63,9 +52,8 @@ func NewClient(addr string, opts ...Option) (*Content, *grpc.ClientConn, error) if err != nil { return nil, nil, err } - dialOpts = append(dialOpts, authDialOpts...) - contentConn, err := grpc.Dial(addr, dialOpts...) + contentConn, err := grpc.Dial(addr, append(config.DialOptions, authDialOpts...)...) if err != nil { return nil, nil, err } @@ -129,79 +117,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 b77d7ddb..e4280505 100644 --- a/pkg/content/config.go +++ b/pkg/content/config.go @@ -1,8 +1,21 @@ package content import ( + "context" + "crypto/tls" + "crypto/x509" + "errors" + "fmt" + "net/url" + + "git.perx.ru/perxis/perxis-go/pkg/transport" kitgrpc "github.com/go-kit/kit/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" ) type Config struct { @@ -14,6 +27,7 @@ type Config struct { NoDecode bool ClientOptions []kitgrpc.ClientOption + DialOptions []grpc.DialOption Logger *zap.Logger } @@ -72,6 +86,17 @@ func AuthTLS(cacert, cert, key []byte) Option { } } +func AuthTLSSkipVerify() Option { + return func(c *Config) { + if c.Auth == nil { + c.Auth = &AuthConfig{} + } + c.Auth.TLS = &TLS{ + SkipVerify: true, + } + } +} + func AuthAPIKey(key string) Option { return func(c *Config) { if c.Auth == nil { @@ -125,3 +150,75 @@ func GrpcClientOptions(opts ...kitgrpc.ClientOption) Option { c.ClientOptions = opts } } + +func GrpcDialOptionsOptions(opts ...grpc.DialOption) Option { + return func(c *Config) { + c.DialOptions = 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 + } + + switch { + case c.Auth.OAuth2 != nil: + // create external grpc client + conf := &clientcredentials.Config{ + TokenURL: c.Auth.OAuth2.TokenURL, + ClientID: c.Auth.OAuth2.ClientID, + ClientSecret: c.Auth.OAuth2.ClientSecret, + EndpointParams: url.Values{"audience": {c.Auth.OAuth2.Audience}}, + } + + // обÑзательно иÑпользовать tls Ð´Ð»Ñ credentials oauth.TokenSource https://github.com/grpc/grpc-go/blob/64031cbfcf4d84c026be93ad7b74b3c290100893/credentials/oauth/oauth.go#L160 + if c.Auth.Insecure { + return nil, errors.New("oauth requires tls") + } + if c.Auth.TLS == nil { + c.Auth.TLS = &TLS{SkipVerify: true} + } + + opts = append(opts, grpc.WithPerRPCCredentials(oauth.TokenSource{TokenSource: conf.TokenSource(ctx)})) + case c.Auth.APIKey != nil: + + if !c.Auth.Insecure && c.Auth.TLS == nil { + c.Auth.TLS = &TLS{SkipVerify: true} + } + opts = append(opts, grpc.WithUnaryInterceptor(transport.AddAuthorizationInterceptor("API-Key "+c.Auth.APIKey.APIKey))) + } + + if c.Auth.TLS != nil { + + if c.Auth.TLS.SkipVerify { + opts = append(opts, grpc.WithTransportCredentials(credentials.NewTLS(&tls.Config{InsecureSkipVerify: true}))) + + } else { + certPool := x509.NewCertPool() + if !certPool.AppendCertsFromPEM(c.Auth.TLS.CaCert) { + return nil, fmt.Errorf("CA certificate not loaded") + } + + clientCert, err := tls.X509KeyPair(c.Auth.TLS.Cert, c.Auth.TLS.Key) + if err != nil { + return nil, err + } + + opts = append(opts, + grpc.WithTransportCredentials(credentials.NewTLS(&tls.Config{ + Certificates: []tls.Certificate{clientCert}, + RootCAs: certPool, + })), + ) + } + } + + if c.Auth.Insecure { + opts = append(opts, grpc.WithTransportCredentials(insecure.NewCredentials())) + } + + return +} diff --git a/pkg/transport/grpc.go b/pkg/transport/grpc.go new file mode 100644 index 00000000..adf54f57 --- /dev/null +++ b/pkg/transport/grpc.go @@ -0,0 +1,15 @@ +package transport + +import ( + "context" + + "google.golang.org/grpc" + "google.golang.org/grpc/metadata" +) + +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...) + } +} diff --git a/pkg/transport/http.go b/pkg/transport/http.go new file mode 100644 index 00000000..032c0515 --- /dev/null +++ b/pkg/transport/http.go @@ -0,0 +1,35 @@ +package transport + +import ( + "bytes" + "io" + "net/http" + "strings" + + "git.perx.ru/perxis/perxis-go/pkg/errors" + jsoniter "github.com/json-iterator/go" +) + +func DecodeError(r *http.Response) error { + if r.StatusCode >= 200 && r.StatusCode < 300 { + return nil + } + + var buf bytes.Buffer + if _, err := io.Copy(&buf, io.LimitReader(r.Body, 1024)); err != nil { + return err + } + + e := struct { + Error error `json:"error"` + }{} + if err := jsoniter.Unmarshal(buf.Bytes(), e); err != nil { + msg := strings.TrimSpace(buf.String()) + if msg == "" { + msg = http.StatusText(r.StatusCode) + } + return errors.New(msg) + } + + return e.Error +} -- GitLab