From 5764519f5b21ca108f543f5f7243f5599308a1c2 Mon Sep 17 00:00:00 2001 From: Ben Toogood Date: Thu, 14 May 2020 11:06:22 +0100 Subject: [PATCH] Refactor auth to load token outside wrappers --- auth/service/service.go | 19 +++------ config/cmd/cmd.go | 42 +++++++++---------- go.mod | 1 + go.sum | 2 + service.go | 89 +++++++++++++++++++++++++++------------- util/config/config.go | 90 ----------------------------------------- util/wrapper/wrapper.go | 40 +----------------- 7 files changed, 91 insertions(+), 192 deletions(-) delete mode 100644 util/config/config.go diff --git a/auth/service/service.go b/auth/service/service.go index ccf2999b..be316525 100644 --- a/auth/service/service.go +++ b/auth/service/service.go @@ -23,7 +23,6 @@ type svc struct { auth pb.AuthService rule pb.RulesService jwt token.Provider - addrs []string rules []*pb.Rule sync.Mutex @@ -71,7 +70,7 @@ func (s *svc) Generate(id string, opts ...auth.GenerateOption) (*auth.Account, e Metadata: options.Metadata, Provider: options.Provider, Namespace: options.Namespace, - }, client.WithAddress(s.addrs...)) + }) if err != nil { return nil, err } @@ -90,7 +89,7 @@ func (s *svc) Grant(role string, res *auth.Resource) error { Name: res.Name, Endpoint: res.Endpoint, }, - }, client.WithAddress(s.addrs...)) + }) return err } @@ -105,7 +104,7 @@ func (s *svc) Revoke(role string, res *auth.Resource) error { Name: res.Name, Endpoint: res.Endpoint, }, - }, client.WithAddress(s.addrs...)) + }) return err } @@ -175,7 +174,7 @@ func (s *svc) Inspect(token string) (*auth.Account, error) { // the token is not a JWT or we do not have the keys to decode it, // fall back to the auth service - rsp, err := s.auth.Inspect(context.TODO(), &pb.InspectRequest{Token: token}, client.WithAddress(s.addrs...)) + rsp, err := s.auth.Inspect(context.TODO(), &pb.InspectRequest{Token: token}) if err != nil { return nil, err } @@ -191,7 +190,7 @@ func (s *svc) Token(opts ...auth.TokenOption) (*auth.Token, error) { Secret: options.Secret, RefreshToken: options.RefreshToken, TokenExpiry: int64(options.Expiry.Seconds()), - }, client.WithAddress(s.addrs...)) + }) if err != nil { return nil, err } @@ -256,7 +255,7 @@ func (s *svc) listRules(filters ...string) []*pb.Rule { // loadRules retrieves the rules from the auth service func (s *svc) loadRules() { - rsp, err := s.rule.List(context.TODO(), &pb.ListRequest{}, client.WithAddress(s.addrs...)) + rsp, err := s.rule.List(context.TODO(), &pb.ListRequest{}) s.Lock() defer s.Unlock() @@ -306,16 +305,10 @@ func NewAuth(opts ...auth.Option) auth.Auth { options.Client = client.DefaultClient } - addrs := options.Addrs - // if len(addrs) == 0 { - // addrs = []string{"127.0.0.1:8010"} - // } - service := &svc{ auth: pb.NewAuthService("go.micro.auth", options.Client), rule: pb.NewRulesService("go.micro.auth", options.Client), options: options, - addrs: addrs, } // load rules periodically from the auth service diff --git a/config/cmd/cmd.go b/config/cmd/cmd.go index 1dfd87c5..de805f6c 100644 --- a/config/cmd/cmd.go +++ b/config/cmd/cmd.go @@ -21,6 +21,7 @@ import ( "github.com/micro/go-micro/v2/debug/trace" "github.com/micro/go-micro/v2/logger" "github.com/micro/go-micro/v2/registry" + 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" @@ -468,6 +469,22 @@ func (c *cmd) Before(ctx *cli.Context) error { var serverOpts []server.Option var clientOpts []client.Option + // Set the client. This must be first since we use the client below + if name := ctx.String("client"); len(name) > 0 { + // only change if we have the client and type differs + if cl, ok := c.opts.Clients[name]; ok && (*c.opts.Client).String() != name { + *c.opts.Client = cl() + } + } + + // Set the server + if name := ctx.String("server"); len(name) > 0 { + // only change if we have the server and type differs + if s, ok := c.opts.Servers[name]; ok && (*c.opts.Server).String() != name { + *c.opts.Server = s() + } + } + // Set the store if name := ctx.String("store"); len(name) > 0 { s, ok := c.opts.Stores[name] @@ -475,7 +492,7 @@ func (c *cmd) Before(ctx *cli.Context) error { return fmt.Errorf("Unsupported store: %s", name) } - *c.opts.Store = s() + *c.opts.Store = s(store.WithClient(*c.opts.Client)) } // Set the runtime @@ -485,7 +502,7 @@ func (c *cmd) Before(ctx *cli.Context) error { return fmt.Errorf("Unsupported runtime: %s", name) } - *c.opts.Runtime = r() + *c.opts.Runtime = r(runtime.WithClient(*c.opts.Client)) } // Set the tracer @@ -504,8 +521,7 @@ func (c *cmd) Before(ctx *cli.Context) error { if !ok { return fmt.Errorf("Unsupported auth: %s", name) } - - *c.opts.Auth = a() + *c.opts.Auth = a(auth.WithClient(*c.opts.Client)) serverOpts = append(serverOpts, server.Auth(*c.opts.Auth)) } @@ -519,22 +535,6 @@ func (c *cmd) Before(ctx *cli.Context) error { *c.opts.Profile = p() } - // Set the client - if name := ctx.String("client"); len(name) > 0 { - // only change if we have the client and type differs - if cl, ok := c.opts.Clients[name]; ok && (*c.opts.Client).String() != name { - *c.opts.Client = cl() - } - } - - // Set the server - if name := ctx.String("server"); len(name) > 0 { - // only change if we have the server and type differs - if s, ok := c.opts.Servers[name]; ok && (*c.opts.Server).String() != name { - *c.opts.Server = s() - } - } - // Set the broker if name := ctx.String("broker"); len(name) > 0 && (*c.opts.Broker).String() != name { b, ok := c.opts.Brokers[name] @@ -554,7 +554,7 @@ func (c *cmd) Before(ctx *cli.Context) error { return fmt.Errorf("Registry %s not found", name) } - *c.opts.Registry = r() + *c.opts.Registry = r(registrySrv.WithClient(*c.opts.Client)) serverOpts = append(serverOpts, server.Registry(*c.opts.Registry)) clientOpts = append(clientOpts, client.Registry(*c.opts.Registry)) diff --git a/go.mod b/go.mod index 4baf1ac9..f549d7df 100644 --- a/go.mod +++ b/go.mod @@ -47,6 +47,7 @@ require ( github.com/stretchr/testify v1.4.0 github.com/tmc/grpc-websocket-proxy v0.0.0-20200122045848-3419fae592fc // indirect go.etcd.io/bbolt v1.3.4 + go.etcd.io/etcd v3.3.20+incompatible go.uber.org/zap v1.13.0 golang.org/x/crypto v0.0.0-20200323165209-0ec3e9974c59 golang.org/x/net v0.0.0-20200301022130-244492dfa37a diff --git a/go.sum b/go.sum index 345168c5..b1a34536 100644 --- a/go.sum +++ b/go.sum @@ -499,6 +499,8 @@ github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.etcd.io/bbolt v1.3.4 h1:hi1bXHMVrlQh6WwxAy+qZCV/SYIlqo+Ushwdpa4tAKg= go.etcd.io/bbolt v1.3.4/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ= +go.etcd.io/etcd v3.3.20+incompatible h1:EyOVslCepyFB2JcbYXvqcYdBTh7cyBKU2NYdKfgTSC0= +go.etcd.io/etcd v3.3.20+incompatible/go.mod h1:yaeTdrJi5lOmYerz05bd8+V7KubZs8YSFZfzsF9A6aI= go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= go.opencensus.io v0.20.2/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= diff --git a/service.go b/service.go index 84347084..ecbd6422 100644 --- a/service.go +++ b/service.go @@ -7,6 +7,7 @@ import ( rtime "runtime" "strings" "sync" + "time" "github.com/micro/go-micro/v2/auth" "github.com/micro/go-micro/v2/client" @@ -51,12 +52,6 @@ func newService(opts ...Option) Service { server.WrapHandler(wrapper.AuthHandler(authFn)), ) - // set the client in the service implementations - // options.Auth.Init(auth.WithClient(options.Client)) - // options.Registry.Init(registrySrv.WithClient(options.Client)) - // options.Runtime.Init(runtime.WithClient(options.Client)) - // options.Store.Init(store.WithClient(options.Client)) - // set opts service.opts = options @@ -119,15 +114,6 @@ func (s *service) Init(opts ...Option) { // Explicitly set the table name to the service name name := s.opts.Cmd.App().Name s.opts.Store.Init(store.Table(name)) - - // Reset the clients for the micro services, this is done - // previously in newService for micro (since init is never called) - // however it needs to be done again here since for normal go-micro - // services the implementation may have changed by CLI flags. - // s.opts.Auth.Init(auth.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())) }) } @@ -240,25 +226,70 @@ func (s *service) Run() error { } func (s *service) generateAccount() error { - // generate a new auth account for the service - name := fmt.Sprintf("%v-%v", s.Name(), s.Server().Options().Id) - opts := []auth.GenerateOption{ - auth.WithType("service"), - auth.WithRoles("service"), - auth.WithNamespace(s.Options().Auth.Options().Namespace), + // extract the account creds from options, these can be set by flags + accID := s.Options().Auth.Options().ID + accSecret := s.Options().Auth.Options().Secret + + // if no credentials were provided, generate an account + if len(accID) == 0 || len(accSecret) == 0 { + name := fmt.Sprintf("%v-%v", s.Name(), s.Server().Options().Id) + opts := []auth.GenerateOption{ + auth.WithType("service"), + auth.WithRoles("service"), + auth.WithNamespace(s.Options().Auth.Options().Namespace), + } + + acc, err := s.Options().Auth.Generate(name, opts...) + if err != nil { + return err + } + logger.Infof("Auth [%v] Authenticated as %v", s.Options().Auth, name) + + accID = acc.ID + accSecret = acc.Secret } - acc, err := s.Options().Auth.Generate(name, opts...) + + // generate the first token + token, err := s.Options().Auth.Token( + auth.WithCredentials(accID, accSecret), + auth.WithExpiry(time.Minute*15), + ) 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 - } + // set the credentials and token in auth options + s.Options().Auth.Init( + auth.ClientToken(token), + auth.Credentials(accID, accSecret), + ) + + // periodically check to see if the token needs refreshing + go func() { + timer := time.NewTicker(time.Second * 15) + + for { + <-timer.C + + // don't refresh the token if it's not close to expiring + if token.Expiry.Unix() > time.Now().Add(time.Minute).Unix() { + continue + } + + // generate the first token + token, err := s.Options().Auth.Token( + auth.WithCredentials(accID, accSecret), + auth.WithExpiry(time.Minute*15), + ) + if err != nil { + logger.Warnf("[Auth] Error refreshing token: %v", err) + continue + } + + // set the token + 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/config/config.go b/util/config/config.go deleted file mode 100644 index a15eade7..00000000 --- a/util/config/config.go +++ /dev/null @@ -1,90 +0,0 @@ -package config - -import ( - "io/ioutil" - "os" - "os/user" - "path/filepath" - "strings" - - conf "github.com/micro/go-micro/v2/config" - "github.com/micro/go-micro/v2/config/source/file" - "github.com/micro/go-micro/v2/util/log" -) - -// FileName for global micro config -const FileName = ".micro" - -// config is a singleton which is required to ensure -// each function call doesn't load the .micro file -// from disk -var config = newConfig() - -// Get a value from the .micro file -func Get(path ...string) (string, error) { - tk := config.Get(path...).String("") - return strings.TrimSpace(tk), nil -} - -// Set a value in the .micro file -func Set(value string, path ...string) error { - // get the filepath - fp, err := filePath() - if err != nil { - return err - } - - // set the value - config.Set(value, path...) - - // write to the file - return ioutil.WriteFile(fp, config.Bytes(), 0644) -} - -func filePath() (string, error) { - usr, err := user.Current() - if err != nil { - return "", err - } - return filepath.Join(usr.HomeDir, FileName), nil -} - -// newConfig returns a loaded config -func newConfig() conf.Config { - // get the filepath - fp, err := filePath() - if err != nil { - log.Error(err) - return conf.DefaultConfig - } - - // write the file if it does not exist - if _, err := os.Stat(fp); os.IsNotExist(err) { - ioutil.WriteFile(fp, []byte{}, 0644) - } else if err != nil { - log.Error(err) - return conf.DefaultConfig - } - - // create a new config - c, err := conf.NewConfig( - conf.WithSource( - file.NewSource( - file.WithPath(fp), - ), - ), - ) - if err != nil { - log.Error(err) - return conf.DefaultConfig - } - - // load the config - if err := c.Load(); err != nil { - log.Error(err) - return conf.DefaultConfig - } - - // return the conf - return c -} diff --git a/util/wrapper/wrapper.go b/util/wrapper/wrapper.go index 6de4a663..041feedd 100644 --- a/util/wrapper/wrapper.go +++ b/util/wrapper/wrapper.go @@ -12,7 +12,6 @@ import ( "github.com/micro/go-micro/v2/errors" "github.com/micro/go-micro/v2/metadata" "github.com/micro/go-micro/v2/server" - "github.com/micro/go-micro/v2/util/config" ) type fromServiceWrapper struct { @@ -159,50 +158,13 @@ func (a *authWrapper) Call(ctx context.Context, req client.Request, rsp interfac return a.Client.Call(ctx, req, rsp, opts...) } - // performs the call with the authorization token provided - callWithToken := func(token string) error { - ctx := metadata.Set(ctx, "Authorization", auth.BearerScheme+token) - return a.Client.Call(ctx, req, rsp, opts...) - } - // check to see if we have a valid access token aaOpts := aa.Options() if aaOpts.Token != nil && aaOpts.Token.Expiry.Unix() > time.Now().Unix() { - return callWithToken(aaOpts.Token.AccessToken) - } - - // check to ensure we're not calling auth, since this will result in - // an endless loop - if req.Service() == "go.micro.auth" { + ctx = metadata.Set(ctx, "Authorization", auth.BearerScheme+aaOpts.Token.AccessToken) return a.Client.Call(ctx, req, rsp, opts...) } - // if we have a refresh token we can use this to generate another access token - if aaOpts.Token != nil { - tok, err := aa.Token(auth.WithToken(aaOpts.Token.RefreshToken)) - if err != nil { - return err - } - aa.Init(auth.ClientToken(tok)) - 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 { - return callWithToken(token) - } - // call without an auth token return a.Client.Call(ctx, req, rsp, opts...) }