From 346e034d0a55560fc52238c81a94181bc80de45b Mon Sep 17 00:00:00 2001 From: Ben Toogood Date: Wed, 13 May 2020 10:40:08 +0100 Subject: [PATCH 1/6] Add mutli-tenancy support to the registry --- registry/service/util.go | 46 +++++++++++++++++++++++++++++++++++++--- util/wrapper/wrapper.go | 4 +++- 2 files changed, 46 insertions(+), 4 deletions(-) diff --git a/registry/service/util.go b/registry/service/util.go index 4ba5f4da..3d6c681d 100644 --- a/registry/service/util.go +++ b/registry/service/util.go @@ -1,6 +1,8 @@ package service import ( + "strings" + "github.com/micro/go-micro/v2/registry" pb "github.com/micro/go-micro/v2/registry/service/proto" ) @@ -37,6 +39,10 @@ func toValues(v []*pb.Value) []*registry.Value { return vs } +// NameSeperator is the string which is used as a seperator when joinin +// namespace to the service name +const NameSeperator = "/" + func ToProto(s *registry.Service) *pb.Service { endpoints := make([]*pb.Endpoint, 0, len(s.Endpoints)) for _, ep := range s.Endpoints { @@ -76,8 +82,14 @@ func ToProto(s *registry.Service) *pb.Service { }) } + // the service name will contain the namespace, e.g. + // 'default/go.micro.service.foo'. Remove the namespace + // using the following: + comps := strings.Split(s.Name, NameSeperator) + name := comps[len(comps)-1] + return &pb.Service{ - Name: s.Name, + Name: name, Version: s.Version, Metadata: s.Metadata, Endpoints: endpoints, @@ -86,7 +98,12 @@ func ToProto(s *registry.Service) *pb.Service { } } -func ToService(s *pb.Service) *registry.Service { +func ToService(s *pb.Service, opts ...Option) *registry.Service { + var options Options + for _, o := range opts { + o(&options) + } + endpoints := make([]*registry.Endpoint, 0, len(s.Endpoints)) for _, ep := range s.Endpoints { var request, response *registry.Value @@ -124,11 +141,34 @@ func ToService(s *pb.Service) *registry.Service { }) } + // add the namespace to the name + var name string + if len(options.Namespace) > 0 { + name = strings.Join([]string{options.Namespace, s.Name}, NameSeperator) + } else { + name = s.Name + } + return ®istry.Service{ - Name: s.Name, + Name: name, Version: s.Version, Metadata: s.Metadata, Endpoints: endpoints, Nodes: nodes, } } + +// Options for marshaling / unmarshaling services +type Options struct { + Namespace string +} + +// Option is a function which sets options +type Option func(o *Options) + +// WithNamespace sets the namespace option +func WithNamespace(ns string) Option { + return func(o *Options) { + o.Namespace = ns + } +} diff --git a/util/wrapper/wrapper.go b/util/wrapper/wrapper.go index ee569ef2..dba8d274 100644 --- a/util/wrapper/wrapper.go +++ b/util/wrapper/wrapper.go @@ -276,7 +276,9 @@ func AuthHandler(fn func() auth.Auth) server.HandlerWrapper { } // There is an account, set it in the context - ctx = auth.ContextWithAccount(ctx, account) + if len(account.ID) > 0 { + ctx = auth.ContextWithAccount(ctx, account) + } // The user is authorised, allow the call return h(ctx, req, rsp) From 54951740bfb4997a6851da9180bb3d0660af8049 Mon Sep 17 00:00:00 2001 From: Ben Toogood Date: Wed, 13 May 2020 13:13:11 +0100 Subject: [PATCH 2/6] Authenticate on service start --- registry/service/options.go | 21 +++++++++++++++ registry/service/service.go | 53 +++++++++++++++++++++++-------------- service.go | 44 +++++++++++++++++++++++++++++- util/wrapper/wrapper.go | 38 ++------------------------ 4 files changed, 99 insertions(+), 57 deletions(-) create mode 100644 registry/service/options.go diff --git a/registry/service/options.go b/registry/service/options.go new file mode 100644 index 00000000..fd251d77 --- /dev/null +++ b/registry/service/options.go @@ -0,0 +1,21 @@ +package service + +import ( + "context" + + "github.com/micro/go-micro/v2/client" + "github.com/micro/go-micro/v2/registry" +) + +type clientKey struct{} + +// WithClient sets the RPC client +func WithClient(c client.Client) registry.Option { + return func(o *registry.Options) { + if o.Context == nil { + o.Context = context.Background() + } + + o.Context = context.WithValue(o.Context, clientKey{}, c) + } +} diff --git a/registry/service/service.go b/registry/service/service.go index acf42d3a..9e2829f4 100644 --- a/registry/service/service.go +++ b/registry/service/service.go @@ -22,8 +22,8 @@ type serviceRegistry struct { name string // address address []string - // client to call registry - client pb.RegistryService + // registry is the proto client + registry pb.RegistryService } func (s *serviceRegistry) callOpts() []client.CallOption { @@ -46,6 +46,17 @@ func (s *serviceRegistry) Init(opts ...registry.Option) error { for _, o := range opts { o(&s.opts) } + + // extract the client from the context, fallback to grpc + var cli client.Client + if c, ok := s.opts.Context.Value(clientKey{}).(client.Client); ok { + cli = c + } else { + cli = grpc.NewClient() + } + + s.registry = pb.NewRegistryService(DefaultService, cli) + return nil } @@ -67,7 +78,7 @@ func (s *serviceRegistry) Register(srv *registry.Service, opts ...registry.Regis pbSrv.Options.Ttl = int64(options.TTL.Seconds()) // register the service - _, err := s.client.Register(options.Context, pbSrv, s.callOpts()...) + _, err := s.registry.Register(options.Context, pbSrv, s.callOpts()...) if err != nil { return err } @@ -85,7 +96,7 @@ func (s *serviceRegistry) Deregister(srv *registry.Service, opts ...registry.Der } // deregister the service - _, err := s.client.Deregister(options.Context, ToProto(srv), s.callOpts()...) + _, err := s.registry.Deregister(options.Context, ToProto(srv), s.callOpts()...) if err != nil { return err } @@ -101,7 +112,7 @@ func (s *serviceRegistry) GetService(name string, opts ...registry.GetOption) ([ options.Context = context.TODO() } - rsp, err := s.client.GetService(options.Context, &pb.GetRequest{ + rsp, err := s.registry.GetService(options.Context, &pb.GetRequest{ Service: name, }, s.callOpts()...) @@ -125,7 +136,7 @@ func (s *serviceRegistry) ListServices(opts ...registry.ListOption) ([]*registry options.Context = context.TODO() } - rsp, err := s.client.ListServices(options.Context, &pb.ListRequest{}, s.callOpts()...) + rsp, err := s.registry.ListServices(options.Context, &pb.ListRequest{}, s.callOpts()...) if err != nil { return nil, err } @@ -147,7 +158,7 @@ func (s *serviceRegistry) Watch(opts ...registry.WatchOption) (registry.Watcher, options.Context = context.TODO() } - stream, err := s.client.Watch(options.Context, &pb.WatchRequest{ + stream, err := s.registry.Watch(options.Context, &pb.WatchRequest{ Service: options.Service, }, s.callOpts()...) @@ -171,27 +182,29 @@ func NewRegistry(opts ...registry.Option) registry.Registry { // the registry address addrs := options.Addrs - if len(addrs) == 0 { addrs = []string{"127.0.0.1:8000"} } - // use mdns as a fall back in case its used - mReg := registry.NewRegistry() + if options.Context == nil { + options.Context = context.TODO() + } - // create new client with mdns - cli := grpc.NewClient( - client.Registry(mReg), - ) + // extract the client from the context, fallback to grpc + var cli client.Client + if c, ok := options.Context.Value(clientKey{}).(client.Client); ok { + cli = c + } else { + cli = grpc.NewClient() + } - // service name - // TODO: accept option + // service name. TODO: accept option name := DefaultService return &serviceRegistry{ - opts: options, - name: name, - address: addrs, - client: pb.NewRegistryService(name, cli), + opts: options, + name: name, + address: addrs, + registry: pb.NewRegistryService(name, cli), } } diff --git a/service.go b/service.go index 00967ea6..2a330870 100644 --- a/service.go +++ b/service.go @@ -1,6 +1,7 @@ package micro import ( + "fmt" "os" "os/signal" rtime "runtime" @@ -15,6 +16,7 @@ import ( "github.com/micro/go-micro/v2/debug/trace" "github.com/micro/go-micro/v2/logger" "github.com/micro/go-micro/v2/plugin" + srvRegistry "github.com/micro/go-micro/v2/registry/service" "github.com/micro/go-micro/v2/runtime" "github.com/micro/go-micro/v2/server" "github.com/micro/go-micro/v2/store" @@ -115,7 +117,8 @@ func (s *service) Init(opts ...Option) { s.opts.Store.Init(store.Table(name)) // Set the client for the micro clients - // s.opts.Auth.Init(auth.WithClient(s.Client())) + s.opts.Auth.Init(auth.WithClient(s.Client())) + s.opts.Registry.Init(srvRegistry.WithClient(s.Client())) s.opts.Runtime.Init(runtime.WithClient(s.Client())) s.opts.Store.Init(store.WithClient(s.Client())) }) @@ -205,6 +208,11 @@ func (s *service) Run() error { logger.Infof("Starting [service] %s", s.Name()) } + // generate an auth account + if err := s.registerAuthAccount(); err != nil { + return err + } + if err := s.Start(); err != nil { return err } @@ -223,3 +231,37 @@ func (s *service) Run() error { return s.Stop() } + +func (s *service) registerAuthAccount() error { + // determine the type of service from the name. we do this so we can allocate + // different roles depending on the type of services. e.g. we don't want web + // services talking directly to the runtime. TODO: find a better way to determine + // the type of service + serviceType := "service" + if strings.Contains(s.Name(), "api") { + serviceType = "api" + } else if strings.Contains(s.Name(), "web") { + serviceType = "web" + } + + // generate a new auth account for the service + name := fmt.Sprintf("%v-%v", s.Name(), s.Server().Options().Id) + opts := []auth.GenerateOption{ + auth.WithRoles(serviceType), + auth.WithNamespace(s.Options().Auth.Options().Namespace), + } + acc, err := s.Options().Auth.Generate(name, opts...) + if err != nil { + return err + } + + // generate a token + token, err := s.Options().Auth.Token(auth.WithCredentials(acc.ID, acc.Secret)) + if err != nil { + return err + } + s.Options().Auth.Init(auth.ClientToken(token)) + + logger.Infof("Auth [%v] Authenticated as %v", s.Options().Auth, name) + return nil +} diff --git a/util/wrapper/wrapper.go b/util/wrapper/wrapper.go index dba8d274..8dde56f0 100644 --- a/util/wrapper/wrapper.go +++ b/util/wrapper/wrapper.go @@ -2,7 +2,6 @@ package wrapper import ( "context" - "fmt" "strings" "time" @@ -182,47 +181,14 @@ func (a *authWrapper) Call(ctx context.Context, req client.Request, rsp interfac return callWithToken(tok.AccessToken) } - // if we have credentials we can generate a new token for the account - if len(aaOpts.ID) > 0 && len(aaOpts.Secret) > 0 { - tok, err := aa.Token(auth.WithCredentials(aaOpts.ID, aaOpts.Secret)) - if err != nil { - return err - } - aa.Init(auth.ClientToken(tok)) - return callWithToken(tok.AccessToken) - } - // check to see if a token was provided in config, this is normally used for // setting the token when calling via the cli if token, err := config.Get("micro", "auth", "token"); err == nil && len(token) > 0 { return callWithToken(token) } - // determine the type of service from the name. we do this so we can allocate - // different roles depending on the type of services. e.g. we don't want web - // services talking directly to the runtime. TODO: find a better way to determine - // the type of service - serviceType := "service" - if strings.Contains(a.name, "api") { - serviceType = "api" - } else if strings.Contains(a.name, "web") { - serviceType = "web" - } - - // generate a new auth account for the service - name := fmt.Sprintf("%v-%v", a.name, a.id) - acc, err := aa.Generate(name, auth.WithNamespace(aaOpts.Namespace), auth.WithRoles(serviceType)) - if err != nil { - return err - } - token, err := aa.Token(auth.WithCredentials(acc.ID, acc.Secret)) - if err != nil { - return err - } - aa.Init(auth.ClientToken(token)) - - // use the token to execute the request - return callWithToken(token.AccessToken) + // call without an auth token + return a.Client.Call(ctx, req, rsp, opts...) } // AuthClient wraps requests with the auth header From d781c9ae2d3ac9ed6bd3868a38d70a87e44e4e75 Mon Sep 17 00:00:00 2001 From: Ben Toogood Date: Wed, 13 May 2020 13:35:34 +0100 Subject: [PATCH 3/6] Remove namespace specific logic --- registry/service/util.go | 46 +++------------------------------------- 1 file changed, 3 insertions(+), 43 deletions(-) diff --git a/registry/service/util.go b/registry/service/util.go index 3d6c681d..4ba5f4da 100644 --- a/registry/service/util.go +++ b/registry/service/util.go @@ -1,8 +1,6 @@ package service import ( - "strings" - "github.com/micro/go-micro/v2/registry" pb "github.com/micro/go-micro/v2/registry/service/proto" ) @@ -39,10 +37,6 @@ func toValues(v []*pb.Value) []*registry.Value { return vs } -// NameSeperator is the string which is used as a seperator when joinin -// namespace to the service name -const NameSeperator = "/" - func ToProto(s *registry.Service) *pb.Service { endpoints := make([]*pb.Endpoint, 0, len(s.Endpoints)) for _, ep := range s.Endpoints { @@ -82,14 +76,8 @@ func ToProto(s *registry.Service) *pb.Service { }) } - // the service name will contain the namespace, e.g. - // 'default/go.micro.service.foo'. Remove the namespace - // using the following: - comps := strings.Split(s.Name, NameSeperator) - name := comps[len(comps)-1] - return &pb.Service{ - Name: name, + Name: s.Name, Version: s.Version, Metadata: s.Metadata, Endpoints: endpoints, @@ -98,12 +86,7 @@ func ToProto(s *registry.Service) *pb.Service { } } -func ToService(s *pb.Service, opts ...Option) *registry.Service { - var options Options - for _, o := range opts { - o(&options) - } - +func ToService(s *pb.Service) *registry.Service { endpoints := make([]*registry.Endpoint, 0, len(s.Endpoints)) for _, ep := range s.Endpoints { var request, response *registry.Value @@ -141,34 +124,11 @@ func ToService(s *pb.Service, opts ...Option) *registry.Service { }) } - // add the namespace to the name - var name string - if len(options.Namespace) > 0 { - name = strings.Join([]string{options.Namespace, s.Name}, NameSeperator) - } else { - name = s.Name - } - return ®istry.Service{ - Name: name, + Name: s.Name, Version: s.Version, Metadata: s.Metadata, Endpoints: endpoints, Nodes: nodes, } } - -// Options for marshaling / unmarshaling services -type Options struct { - Namespace string -} - -// Option is a function which sets options -type Option func(o *Options) - -// WithNamespace sets the namespace option -func WithNamespace(ns string) Option { - return func(o *Options) { - o.Namespace = ns - } -} From 25c937fd0edf9e2ff5c4c1779dcfc09628809163 Mon Sep 17 00:00:00 2001 From: Ben Toogood Date: Wed, 13 May 2020 13:38:13 +0100 Subject: [PATCH 4/6] Naming changes --- registry/service/service.go | 24 ++++++++++++------------ service.go | 4 ++-- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/registry/service/service.go b/registry/service/service.go index 9e2829f4..ddf6fd00 100644 --- a/registry/service/service.go +++ b/registry/service/service.go @@ -22,8 +22,8 @@ type serviceRegistry struct { name string // address address []string - // registry is the proto client - registry pb.RegistryService + // client to call registry + client pb.RegistryService } func (s *serviceRegistry) callOpts() []client.CallOption { @@ -55,7 +55,7 @@ func (s *serviceRegistry) Init(opts ...registry.Option) error { cli = grpc.NewClient() } - s.registry = pb.NewRegistryService(DefaultService, cli) + s.client = pb.NewRegistryService(DefaultService, cli) return nil } @@ -78,7 +78,7 @@ func (s *serviceRegistry) Register(srv *registry.Service, opts ...registry.Regis pbSrv.Options.Ttl = int64(options.TTL.Seconds()) // register the service - _, err := s.registry.Register(options.Context, pbSrv, s.callOpts()...) + _, err := s.client.Register(options.Context, pbSrv, s.callOpts()...) if err != nil { return err } @@ -96,7 +96,7 @@ func (s *serviceRegistry) Deregister(srv *registry.Service, opts ...registry.Der } // deregister the service - _, err := s.registry.Deregister(options.Context, ToProto(srv), s.callOpts()...) + _, err := s.client.Deregister(options.Context, ToProto(srv), s.callOpts()...) if err != nil { return err } @@ -112,7 +112,7 @@ func (s *serviceRegistry) GetService(name string, opts ...registry.GetOption) ([ options.Context = context.TODO() } - rsp, err := s.registry.GetService(options.Context, &pb.GetRequest{ + rsp, err := s.client.GetService(options.Context, &pb.GetRequest{ Service: name, }, s.callOpts()...) @@ -136,7 +136,7 @@ func (s *serviceRegistry) ListServices(opts ...registry.ListOption) ([]*registry options.Context = context.TODO() } - rsp, err := s.registry.ListServices(options.Context, &pb.ListRequest{}, s.callOpts()...) + rsp, err := s.client.ListServices(options.Context, &pb.ListRequest{}, s.callOpts()...) if err != nil { return nil, err } @@ -158,7 +158,7 @@ func (s *serviceRegistry) Watch(opts ...registry.WatchOption) (registry.Watcher, options.Context = context.TODO() } - stream, err := s.registry.Watch(options.Context, &pb.WatchRequest{ + stream, err := s.client.Watch(options.Context, &pb.WatchRequest{ Service: options.Service, }, s.callOpts()...) @@ -202,9 +202,9 @@ func NewRegistry(opts ...registry.Option) registry.Registry { name := DefaultService return &serviceRegistry{ - opts: options, - name: name, - address: addrs, - registry: pb.NewRegistryService(name, cli), + opts: options, + name: name, + address: addrs, + client: pb.NewRegistryService(name, cli), } } diff --git a/service.go b/service.go index 2a330870..c1458810 100644 --- a/service.go +++ b/service.go @@ -16,7 +16,7 @@ import ( "github.com/micro/go-micro/v2/debug/trace" "github.com/micro/go-micro/v2/logger" "github.com/micro/go-micro/v2/plugin" - srvRegistry "github.com/micro/go-micro/v2/registry/service" + registrySrv "github.com/micro/go-micro/v2/registry/service" "github.com/micro/go-micro/v2/runtime" "github.com/micro/go-micro/v2/server" "github.com/micro/go-micro/v2/store" @@ -118,7 +118,7 @@ func (s *service) Init(opts ...Option) { // Set the client for the micro clients s.opts.Auth.Init(auth.WithClient(s.Client())) - s.opts.Registry.Init(srvRegistry.WithClient(s.Client())) + s.opts.Registry.Init(registrySrv.WithClient(s.Client())) s.opts.Runtime.Init(runtime.WithClient(s.Client())) s.opts.Store.Init(store.WithClient(s.Client())) }) From 3fac7d79ab317081961a7ec64eea26e8b3c6d778 Mon Sep 17 00:00:00 2001 From: Ben Toogood Date: Wed, 13 May 2020 13:42:56 +0100 Subject: [PATCH 5/6] Remove service type role --- service.go | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/service.go b/service.go index c1458810..17b614d1 100644 --- a/service.go +++ b/service.go @@ -233,21 +233,11 @@ func (s *service) Run() error { } func (s *service) registerAuthAccount() error { - // determine the type of service from the name. we do this so we can allocate - // different roles depending on the type of services. e.g. we don't want web - // services talking directly to the runtime. TODO: find a better way to determine - // the type of service - serviceType := "service" - if strings.Contains(s.Name(), "api") { - serviceType = "api" - } else if strings.Contains(s.Name(), "web") { - serviceType = "web" - } - // generate a new auth account for the service name := fmt.Sprintf("%v-%v", s.Name(), s.Server().Options().Id) opts := []auth.GenerateOption{ - auth.WithRoles(serviceType), + auth.WithType("service"), + auth.WithRoles("service"), auth.WithNamespace(s.Options().Auth.Options().Namespace), } acc, err := s.Options().Auth.Generate(name, opts...) From 47c1cb433e3c2728288a48e5480ba86662d6579f Mon Sep 17 00:00:00 2001 From: Ben Toogood Date: Wed, 13 May 2020 13:48:25 +0100 Subject: [PATCH 6/6] Store account credentials --- service.go | 2 +- util/wrapper/wrapper.go | 10 ++++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/service.go b/service.go index 17b614d1..75de60fa 100644 --- a/service.go +++ b/service.go @@ -250,8 +250,8 @@ func (s *service) registerAuthAccount() error { if err != nil { return err } - s.Options().Auth.Init(auth.ClientToken(token)) + s.Options().Auth.Init(auth.ClientToken(token), auth.Credentials(acc.ID, acc.Secret)) logger.Infof("Auth [%v] Authenticated as %v", s.Options().Auth, name) return nil } diff --git a/util/wrapper/wrapper.go b/util/wrapper/wrapper.go index 8dde56f0..285a775f 100644 --- a/util/wrapper/wrapper.go +++ b/util/wrapper/wrapper.go @@ -181,6 +181,16 @@ func (a *authWrapper) Call(ctx context.Context, req client.Request, rsp interfac return callWithToken(tok.AccessToken) } + // generate a new token if we have credentials + if len(aaOpts.ID) > 0 && len(aaOpts.Secret) > 0 { + tok, err := aa.Token(auth.WithCredentials(aaOpts.ID, aaOpts.Secret)) + if err != nil { + return err + } + aa.Init(auth.ClientToken(tok)) + return callWithToken(tok.AccessToken) + } + // check to see if a token was provided in config, this is normally used for // setting the token when calling via the cli if token, err := config.Get("micro", "auth", "token"); err == nil && len(token) > 0 {