Skip to content
Snippets Groups Projects
Commit 52472584 authored by Pavel Antonov's avatar Pavel Antonov :asterisk:
Browse files

Merge branch 'feature/1148-PerxisClients' into 'master'

Добавлены опции для конфигурации клиентов Account, Content

See merge request perxis/perxis-go!39
parents 75f31532 90e79f39
No related branches found
No related tags found
No related merge requests found
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 {
......
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
}
......
......@@ -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
}
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
}
......@@ -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
......
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{
......
......@@ -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...))
}
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment