From d659e435c6604c264043c12b16ad631330293996 Mon Sep 17 00:00:00 2001 From: Ben Toogood Date: Tue, 31 Mar 2020 12:44:34 +0100 Subject: [PATCH 1/5] Service => Service Auth --- auth/options.go | 15 ++++++++---- auth/service/service.go | 52 +++++++++++++++++++++++++++++++++++++++-- client/grpc/grpc.go | 10 +++++++- client/options.go | 20 ++++++++++++++++ config/cmd/cmd.go | 18 ++++++++++---- options.go | 1 + service.go | 7 +++--- util/wrapper/wrapper.go | 9 ------- 8 files changed, 106 insertions(+), 26 deletions(-) diff --git a/auth/options.go b/auth/options.go index 274935b3..90bbc1df 100644 --- a/auth/options.go +++ b/auth/options.go @@ -8,8 +8,12 @@ import ( ) type Options struct { - // Token is an auth token - Token string + // ID is the services auth ID + ID string + // Secret is used to generate new tokens + Secret string + // Token is the services token used to authenticate itself + Token *Token // Public key base64 encoded PublicKey string // Private key base64 encoded @@ -45,10 +49,11 @@ func PrivateKey(key string) Option { } } -// ServiceToken sets an auth token -func ServiceToken(t string) Option { +// Credentials sets the auth credentials +func Credentials(id, secret string) Option { return func(o *Options) { - o.Token = t + o.ID = id + o.Secret = secret } } diff --git a/auth/service/service.go b/auth/service/service.go index 2aade9fc..0cc11d98 100644 --- a/auth/service/service.go +++ b/auth/service/service.go @@ -55,13 +55,13 @@ func (s *svc) Init(opts ...auth.Option) { } // load rules periodically from the auth service - timer := time.NewTicker(time.Second * 30) + ruleTimer := time.NewTicker(time.Second * 30) go func() { // load rules immediately on startup s.loadRules() for { - <-timer.C + <-ruleTimer.C // jitter for up to 5 seconds, this stops // all the services calling the auth service @@ -70,9 +70,39 @@ func (s *svc) Init(opts ...auth.Option) { s.loadRules() } }() + + // we have client credentials and must load a new token + // periodically + if len(s.options.ID) > 0 || len(s.options.Secret) > 0 { + tokenTimer := time.NewTicker(time.Minute) + + go func() { + s.loadToken() + + for { + <-tokenTimer.C + + // Do not get a new token if the current one has more than three + // minutes remaining. We do 3 minutes to allow multiple retires in + // the case one request fails + t := s.Options().Token + if t != nil && t.Expiry.Unix() > time.Now().Add(time.Minute*3).Unix() { + continue + } + + // jitter for up to 5 seconds, this stops + // all the services calling the auth service + // at the exact same time + time.Sleep(jitter.Do(time.Second * 5)) + s.loadToken() + } + }() + } } func (s *svc) Options() auth.Options { + s.Lock() + defer s.Unlock() return s.options } @@ -256,6 +286,24 @@ func (s *svc) loadRules() { s.rules = rsp.Rules } +// loadToken generates a new token for the service to use when making calls +func (s *svc) loadToken() { + rsp, err := s.auth.Token(context.TODO(), &pb.TokenRequest{ + Id: s.Options().ID, + Secret: s.Options().Secret, + TokenExpiry: int64((time.Minute * 15).Seconds()), + }) + s.Lock() + defer s.Unlock() + + if err != nil { + log.Errorf("Error generating token: %v", err) + return + } + + s.options.Token = serializeToken(rsp.Token) +} + func serializeToken(t *pb.Token) *auth.Token { return &auth.Token{ Token: t.Token, diff --git a/client/grpc/grpc.go b/client/grpc/grpc.go index 0357abc3..acc1e649 100644 --- a/client/grpc/grpc.go +++ b/client/grpc/grpc.go @@ -131,7 +131,15 @@ func (g *grpcClient) call(ctx context.Context, node *registry.Node, req client.R // set the content type for the request header["x-content-type"] = req.ContentType() - // set the authorization token if one is saved locally + // if the caller specifies using service privelages, and the client + // has auth set, override the authorization header + if opts.WithServicePrivileges && g.opts.Auth != nil && g.opts.Auth.Options().Token != nil { + t := g.opts.Auth.Options().Token + header["authorization"] = auth.BearerScheme + t.Token + } + + // fall back to using the authorization token set in config, + // this enables the CLI to provide a token if len(header["authorization"]) == 0 { if token, err := config.Get("token"); err == nil && len(token) > 0 { header["authorization"] = auth.BearerScheme + token diff --git a/client/options.go b/client/options.go index 2d201510..1366ad56 100644 --- a/client/options.go +++ b/client/options.go @@ -4,6 +4,7 @@ import ( "context" "time" + "github.com/micro/go-micro/v2/auth" "github.com/micro/go-micro/v2/broker" "github.com/micro/go-micro/v2/client/selector" "github.com/micro/go-micro/v2/codec" @@ -16,6 +17,7 @@ type Options struct { ContentType string // Plugged interfaces + Auth auth.Auth Broker broker.Broker Codecs map[string]codec.NewCodec Registry registry.Registry @@ -55,6 +57,8 @@ type CallOptions struct { Retries int // Request/Response timeout RequestTimeout time.Duration + // Use the services own auth token + WithServicePrivileges bool // Middleware for low level call func CallWrappers []CallWrapper @@ -99,6 +103,7 @@ func NewOptions(options ...Option) Options { }, PoolSize: DefaultPoolSize, PoolTTL: DefaultPoolTTL, + Auth: auth.DefaultAuth, Broker: broker.DefaultBroker, Selector: selector.DefaultSelector, Registry: registry.DefaultRegistry, @@ -119,6 +124,13 @@ func Broker(b broker.Broker) Option { } } +// Auth to be used when making a request +func Auth(a auth.Auth) Option { + return func(o *Options) { + o.Auth = a + } +} + // Codec to be used to encode/decode requests for a given content type func Codec(contentType string, c codec.NewCodec) Option { return func(o *Options) { @@ -291,6 +303,14 @@ func WithDialTimeout(d time.Duration) CallOption { } } +// WithServicePrivileges is a CallOption which overrides the +// authorization header with the services own auth token +func WithServicePrivileges() CallOption { + return func(o *CallOptions) { + o.WithServicePrivileges = true + } +} + func WithMessageContentType(ct string) MessageOption { return func(o *MessageOptions) { o.ContentType = ct diff --git a/config/cmd/cmd.go b/config/cmd/cmd.go index 287c085e..0af37fdd 100644 --- a/config/cmd/cmd.go +++ b/config/cmd/cmd.go @@ -255,9 +255,14 @@ var ( Usage: "Auth for role based access control, e.g. service", }, &cli.StringFlag{ - Name: "auth_token", - EnvVars: []string{"MICRO_AUTH_TOKEN"}, - Usage: "Auth token used for client authentication", + Name: "auth_id", + EnvVars: []string{"MICRO_AUTH_ID"}, + Usage: "Account ID used for client authentication", + }, + &cli.StringFlag{ + Name: "auth_secret", + EnvVars: []string{"MICRO_AUTH_SECRET"}, + Usage: "Account secret used for client authentication", }, &cli.StringFlag{ Name: "auth_public_key", @@ -488,6 +493,7 @@ func (c *cmd) Before(ctx *cli.Context) error { } *c.opts.Auth = a() + clientOpts = append(clientOpts, client.Auth(*c.opts.Auth)) } // Set the profile @@ -655,8 +661,10 @@ func (c *cmd) Before(ctx *cli.Context) error { } } - if len(ctx.String("auth_token")) > 0 { - authOpts = append(authOpts, auth.ServiceToken(ctx.String("auth_token"))) + if len(ctx.String("auth_id")) > 0 || len(ctx.String("auth_secret")) > 0 { + authOpts = append(authOpts, auth.Credentials( + ctx.String("auth_id"), ctx.String("auth_secret"), + )) } if len(ctx.String("auth_public_key")) > 0 { diff --git a/options.go b/options.go index 36e290a3..1ef8b955 100644 --- a/options.go +++ b/options.go @@ -142,6 +142,7 @@ func Tracer(t trace.Tracer) Option { func Auth(a auth.Auth) Option { return func(o *Options) { o.Auth = a + o.Client.Init(client.Auth(a)) o.Server.Init(server.Auth(a)) } } diff --git a/service.go b/service.go index a693aa34..a13b4e19 100644 --- a/service.go +++ b/service.go @@ -18,7 +18,6 @@ import ( "github.com/micro/go-micro/v2/plugin" "github.com/micro/go-micro/v2/server" "github.com/micro/go-micro/v2/store" - "github.com/micro/go-micro/v2/util/config" "github.com/micro/go-micro/v2/util/wrapper" ) @@ -117,9 +116,9 @@ func (s *service) Init(opts ...Option) { // Right now we're just going to load a token // May need to re-read value on change // TODO: should be scoped to micro/auth/token - if tk, _ := config.Get("token"); len(tk) > 0 { - s.opts.Auth.Init(auth.ServiceToken(tk)) - } + // if tk, _ := config.Get("token"); len(tk) > 0 { + // s.opts.Auth.Init(auth.ServiceToken(tk)) + // } }) } diff --git a/util/wrapper/wrapper.go b/util/wrapper/wrapper.go index d28155c1..f795e705 100644 --- a/util/wrapper/wrapper.go +++ b/util/wrapper/wrapper.go @@ -38,15 +38,6 @@ func (c *clientWrapper) setHeaders(ctx context.Context) context.Context { mda, _ := metadata.FromContext(ctx) md := metadata.Copy(mda) - // get auth token - if a := c.auth(); a != nil { - tk := a.Options().Token - // if the token if exists and auth header isn't set then set it - if len(tk) > 0 && len(md["Authorization"]) == 0 { - md["Authorization"] = auth.BearerScheme + tk - } - } - // set headers for k, v := range c.headers { if _, ok := md[k]; !ok { From e0c7f48d20eb99718c4a5b491e4ed69f164a8c1f Mon Sep 17 00:00:00 2001 From: Ben Toogood Date: Tue, 31 Mar 2020 12:57:38 +0100 Subject: [PATCH 2/5] WithServicePrivileges => ServicePrivileges --- client/grpc/grpc.go | 2 +- client/options.go | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/client/grpc/grpc.go b/client/grpc/grpc.go index acc1e649..340f2df7 100644 --- a/client/grpc/grpc.go +++ b/client/grpc/grpc.go @@ -133,7 +133,7 @@ func (g *grpcClient) call(ctx context.Context, node *registry.Node, req client.R // if the caller specifies using service privelages, and the client // has auth set, override the authorization header - if opts.WithServicePrivileges && g.opts.Auth != nil && g.opts.Auth.Options().Token != nil { + if opts.ServicePrivileges && g.opts.Auth != nil && g.opts.Auth.Options().Token != nil { t := g.opts.Auth.Options().Token header["authorization"] = auth.BearerScheme + t.Token } diff --git a/client/options.go b/client/options.go index 1366ad56..72d9d2bd 100644 --- a/client/options.go +++ b/client/options.go @@ -58,7 +58,7 @@ type CallOptions struct { // Request/Response timeout RequestTimeout time.Duration // Use the services own auth token - WithServicePrivileges bool + ServicePrivileges bool // Middleware for low level call func CallWrappers []CallWrapper @@ -307,7 +307,7 @@ func WithDialTimeout(d time.Duration) CallOption { // authorization header with the services own auth token func WithServicePrivileges() CallOption { return func(o *CallOptions) { - o.WithServicePrivileges = true + o.ServicePrivileges = true } } From 956029ae3dc70481582ed890e64dfb45a2f51dd0 Mon Sep 17 00:00:00 2001 From: Ben Toogood Date: Tue, 31 Mar 2020 13:30:14 +0100 Subject: [PATCH 3/5] Fixes for CLI login --- client/grpc/grpc.go | 2 +- util/config/config.go | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/client/grpc/grpc.go b/client/grpc/grpc.go index 340f2df7..54c4599d 100644 --- a/client/grpc/grpc.go +++ b/client/grpc/grpc.go @@ -141,7 +141,7 @@ func (g *grpcClient) call(ctx context.Context, node *registry.Node, req client.R // fall back to using the authorization token set in config, // this enables the CLI to provide a token if len(header["authorization"]) == 0 { - if token, err := config.Get("token"); err == nil && len(token) > 0 { + if token, err := config.Get("micro", "auth", "token"); err == nil && len(token) > 0 { header["authorization"] = auth.BearerScheme + token } } diff --git a/util/config/config.go b/util/config/config.go index e0e85e37..a15eade7 100644 --- a/util/config/config.go +++ b/util/config/config.go @@ -21,13 +21,13 @@ const FileName = ".micro" var config = newConfig() // Get a value from the .micro file -func Get(key string) (string, error) { - tk := config.Get(key).String("") +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(key, value string) error { +func Set(value string, path ...string) error { // get the filepath fp, err := filePath() if err != nil { @@ -35,7 +35,7 @@ func Set(key, value string) error { } // set the value - config.Set(value, key) + config.Set(value, path...) // write to the file return ioutil.WriteFile(fp, config.Bytes(), 0644) From bd70820b6bd035b3d7802e6add6ef30ffa31a67e Mon Sep 17 00:00:00 2001 From: Ben Toogood Date: Tue, 31 Mar 2020 13:48:28 +0100 Subject: [PATCH 4/5] ServicePrivileges => ServiceToken --- client/grpc/grpc.go | 2 +- client/options.go | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/client/grpc/grpc.go b/client/grpc/grpc.go index 54c4599d..8cd55dd8 100644 --- a/client/grpc/grpc.go +++ b/client/grpc/grpc.go @@ -133,7 +133,7 @@ func (g *grpcClient) call(ctx context.Context, node *registry.Node, req client.R // if the caller specifies using service privelages, and the client // has auth set, override the authorization header - if opts.ServicePrivileges && g.opts.Auth != nil && g.opts.Auth.Options().Token != nil { + if opts.ServiceToken && g.opts.Auth != nil && g.opts.Auth.Options().Token != nil { t := g.opts.Auth.Options().Token header["authorization"] = auth.BearerScheme + t.Token } diff --git a/client/options.go b/client/options.go index 72d9d2bd..378957ed 100644 --- a/client/options.go +++ b/client/options.go @@ -58,7 +58,7 @@ type CallOptions struct { // Request/Response timeout RequestTimeout time.Duration // Use the services own auth token - ServicePrivileges bool + ServiceToken bool // Middleware for low level call func CallWrappers []CallWrapper @@ -303,11 +303,11 @@ func WithDialTimeout(d time.Duration) CallOption { } } -// WithServicePrivileges is a CallOption which overrides the +// WithServiceToken is a CallOption which overrides the // authorization header with the services own auth token -func WithServicePrivileges() CallOption { +func WithServiceToken() CallOption { return func(o *CallOptions) { - o.ServicePrivileges = true + o.ServiceToken = true } } From 36386354d72e9d6ea72631a09bbbd810a5724c3c Mon Sep 17 00:00:00 2001 From: Ben Toogood Date: Tue, 31 Mar 2020 13:51:32 +0100 Subject: [PATCH 5/5] Fallback to service token --- client/grpc/grpc.go | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/client/grpc/grpc.go b/client/grpc/grpc.go index 8cd55dd8..5f14c25d 100644 --- a/client/grpc/grpc.go +++ b/client/grpc/grpc.go @@ -131,11 +131,14 @@ func (g *grpcClient) call(ctx context.Context, node *registry.Node, req client.R // set the content type for the request header["x-content-type"] = req.ContentType() - // if the caller specifies using service privelages, and the client - // has auth set, override the authorization header - if opts.ServiceToken && g.opts.Auth != nil && g.opts.Auth.Options().Token != nil { - t := g.opts.Auth.Options().Token - header["authorization"] = auth.BearerScheme + t.Token + // if the caller specifies using service token or no token + // was passed with the request, set the service token + var srvToken string + if g.opts.Auth != nil && g.opts.Auth.Options().Token != nil { + srvToken = g.opts.Auth.Options().Token.Token + } + if (opts.ServiceToken || len(header["authorization"]) == 0) && len(srvToken) > 0 { + header["authorization"] = auth.BearerScheme + srvToken } // fall back to using the authorization token set in config,