diff --git a/auth/jwt/jwt.go b/auth/jwt/jwt.go index 86815bce..9c118e1a 100644 --- a/auth/jwt/jwt.go +++ b/auth/jwt/jwt.go @@ -2,6 +2,7 @@ package jwt import ( "sync" + "time" "github.com/micro/go-micro/v2/auth" "github.com/micro/go-micro/v2/auth/token" @@ -176,15 +177,20 @@ func (j *jwt) Token(opts ...auth.TokenOption) (*auth.Token, error) { return nil, err } - tok, err := j.jwt.Generate(account, token.WithExpiry(options.Expiry)) + access, err := j.jwt.Generate(account, token.WithExpiry(options.Expiry)) + if err != nil { + return nil, err + } + + refresh, err := j.jwt.Generate(account, token.WithExpiry(options.Expiry+time.Hour)) if err != nil { return nil, err } return &auth.Token{ - Created: tok.Created, - Expiry: tok.Expiry, - AccessToken: tok.Token, - RefreshToken: tok.Token, + Created: access.Created, + Expiry: access.Expiry, + AccessToken: access.Token, + RefreshToken: refresh.Token, }, nil } diff --git a/auth/service/service.go b/auth/service/service.go index ccf2999b..482b488f 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() @@ -301,21 +300,14 @@ func serializeAccount(a *pb.Account) *auth.Account { // NewAuth returns a new instance of the Auth service func NewAuth(opts ...auth.Option) auth.Auth { options := auth.NewOptions(opts...) - if options.Client == nil { 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 @@ -323,9 +315,9 @@ func NewAuth(opts ...auth.Option) auth.Auth { ruleTimer := time.NewTicker(time.Second * 30) for { + <-ruleTimer.C time.Sleep(jitter.Do(time.Second * 5)) service.loadRules() - <-ruleTimer.C } }() diff --git a/config/cmd/cmd.go b/config/cmd/cmd.go index 1dfd87c5..6ee8bd92 100644 --- a/config/cmd/cmd.go +++ b/config/cmd/cmd.go @@ -11,6 +11,7 @@ import ( "github.com/micro/go-micro/v2/auth/provider" "github.com/micro/go-micro/v2/broker" "github.com/micro/go-micro/v2/client" + "github.com/micro/go-micro/v2/client/grpc" "github.com/micro/go-micro/v2/client/selector" "github.com/micro/go-micro/v2/config" configSrc "github.com/micro/go-micro/v2/config/source" @@ -21,10 +22,12 @@ 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" "github.com/micro/go-micro/v2/transport" + "github.com/micro/go-micro/v2/util/wrapper" // clients cgrpc "github.com/micro/go-micro/v2/client/grpc" @@ -468,6 +471,10 @@ func (c *cmd) Before(ctx *cli.Context) error { var serverOpts []server.Option var clientOpts []client.Option + // setup a client to use when calling the runtime + authFn := func() auth.Auth { return *c.opts.Auth } + microClient := wrapper.AuthClient(authFn, grpc.NewClient()) + // Set the store if name := ctx.String("store"); len(name) > 0 { s, ok := c.opts.Stores[name] @@ -475,7 +482,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(microClient)) } // Set the runtime @@ -485,7 +492,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(microClient)) } // Set the tracer @@ -504,8 +511,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(microClient)) serverOpts = append(serverOpts, server.Auth(*c.opts.Auth)) } @@ -554,7 +560,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(microClient)) serverOpts = append(serverOpts, server.Registry(*c.opts.Registry)) clientOpts = append(clientOpts, client.Registry(*c.opts.Registry)) @@ -725,7 +731,7 @@ func (c *cmd) Before(ctx *cli.Context) error { (*c.opts.Auth).Init(authOpts...) if ctx.String("config") == "service" { - opt := config.WithSource(configSrv.NewSource(configSrc.WithClient(*c.opts.Client))) + opt := config.WithSource(configSrv.NewSource(configSrc.WithClient(microClient))) if err := (*c.opts.Config).Init(opt); err != nil { logger.Fatalf("Error configuring config: %v", err) } diff --git a/service.go b/service.go index 84347084..f37dc83b 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" @@ -42,7 +43,7 @@ func newService(opts ...Option) Service { // wrap client to inject From-Service header on any calls options.Client = wrapper.FromService(serviceName, options.Client) options.Client = wrapper.TraceCall(serviceName, trace.DefaultTracer, options.Client) - options.Client = wrapper.AuthClient(serviceName, options.Server.Options().Id, authFn, options.Client) + options.Client = wrapper.AuthClient(authFn, options.Client) // wrap the server to provide handler stats options.Server.Init( @@ -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,71 @@ 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*10), + ) 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 + tok := s.Options().Auth.Options().Token + if tok.Expiry.Unix() > time.Now().Add(time.Minute).Unix() { + continue + } + + // generate the first token + tok, err := s.Options().Auth.Token( + auth.WithCredentials(accID, accSecret), + auth.WithExpiry(time.Minute*10), + ) + if err != nil { + logger.Warnf("[Auth] Error refreshing token: %v", err) + continue + } + + // set the token + s.Options().Auth.Init(auth.ClientToken(tok)) + } + }() - 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..51672f0a 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 { @@ -133,8 +132,6 @@ func TraceHandler(t trace.Tracer) server.HandlerWrapper { type authWrapper struct { client.Client - name string - id string auth func() auth.Auth } @@ -159,57 +156,20 @@ 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...) } // AuthClient wraps requests with the auth header -func AuthClient(name string, id string, auth func() auth.Auth, c client.Client) client.Client { - return &authWrapper{c, name, id, auth} +func AuthClient(auth func() auth.Auth, c client.Client) client.Client { + return &authWrapper{c, auth} } // AuthHandler wraps a server handler to perform auth