Service => Service Auth
This commit is contained in:
		| @@ -8,8 +8,12 @@ import ( | |||||||
| ) | ) | ||||||
|  |  | ||||||
| type Options struct { | type Options struct { | ||||||
| 	// Token is an auth token | 	// ID is the services auth ID | ||||||
| 	Token string | 	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 | 	// Public key base64 encoded | ||||||
| 	PublicKey string | 	PublicKey string | ||||||
| 	// Private key base64 encoded | 	// Private key base64 encoded | ||||||
| @@ -45,10 +49,11 @@ func PrivateKey(key string) Option { | |||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
| // ServiceToken sets an auth token | // Credentials sets the auth credentials | ||||||
| func ServiceToken(t string) Option { | func Credentials(id, secret string) Option { | ||||||
| 	return func(o *Options) { | 	return func(o *Options) { | ||||||
| 		o.Token = t | 		o.ID = id | ||||||
|  | 		o.Secret = secret | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -55,13 +55,13 @@ func (s *svc) Init(opts ...auth.Option) { | |||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	// load rules periodically from the auth service | 	// load rules periodically from the auth service | ||||||
| 	timer := time.NewTicker(time.Second * 30) | 	ruleTimer := time.NewTicker(time.Second * 30) | ||||||
| 	go func() { | 	go func() { | ||||||
| 		// load rules immediately on startup | 		// load rules immediately on startup | ||||||
| 		s.loadRules() | 		s.loadRules() | ||||||
|  |  | ||||||
| 		for { | 		for { | ||||||
| 			<-timer.C | 			<-ruleTimer.C | ||||||
|  |  | ||||||
| 			// jitter for up to 5 seconds, this stops | 			// jitter for up to 5 seconds, this stops | ||||||
| 			// all the services calling the auth service | 			// all the services calling the auth service | ||||||
| @@ -70,9 +70,39 @@ func (s *svc) Init(opts ...auth.Option) { | |||||||
| 			s.loadRules() | 			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 { | func (s *svc) Options() auth.Options { | ||||||
|  | 	s.Lock() | ||||||
|  | 	defer s.Unlock() | ||||||
| 	return s.options | 	return s.options | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -256,6 +286,24 @@ func (s *svc) loadRules() { | |||||||
| 	s.rules = rsp.Rules | 	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 { | func serializeToken(t *pb.Token) *auth.Token { | ||||||
| 	return &auth.Token{ | 	return &auth.Token{ | ||||||
| 		Token:    t.Token, | 		Token:    t.Token, | ||||||
|   | |||||||
| @@ -131,7 +131,15 @@ func (g *grpcClient) call(ctx context.Context, node *registry.Node, req client.R | |||||||
| 	// set the content type for the request | 	// set the content type for the request | ||||||
| 	header["x-content-type"] = req.ContentType() | 	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 len(header["authorization"]) == 0 { | ||||||
| 		if token, err := config.Get("token"); err == nil && len(token) > 0 { | 		if token, err := config.Get("token"); err == nil && len(token) > 0 { | ||||||
| 			header["authorization"] = auth.BearerScheme + token | 			header["authorization"] = auth.BearerScheme + token | ||||||
|   | |||||||
| @@ -4,6 +4,7 @@ import ( | |||||||
| 	"context" | 	"context" | ||||||
| 	"time" | 	"time" | ||||||
|  |  | ||||||
|  | 	"github.com/micro/go-micro/v2/auth" | ||||||
| 	"github.com/micro/go-micro/v2/broker" | 	"github.com/micro/go-micro/v2/broker" | ||||||
| 	"github.com/micro/go-micro/v2/client/selector" | 	"github.com/micro/go-micro/v2/client/selector" | ||||||
| 	"github.com/micro/go-micro/v2/codec" | 	"github.com/micro/go-micro/v2/codec" | ||||||
| @@ -16,6 +17,7 @@ type Options struct { | |||||||
| 	ContentType string | 	ContentType string | ||||||
|  |  | ||||||
| 	// Plugged interfaces | 	// Plugged interfaces | ||||||
|  | 	Auth      auth.Auth | ||||||
| 	Broker    broker.Broker | 	Broker    broker.Broker | ||||||
| 	Codecs    map[string]codec.NewCodec | 	Codecs    map[string]codec.NewCodec | ||||||
| 	Registry  registry.Registry | 	Registry  registry.Registry | ||||||
| @@ -55,6 +57,8 @@ type CallOptions struct { | |||||||
| 	Retries int | 	Retries int | ||||||
| 	// Request/Response timeout | 	// Request/Response timeout | ||||||
| 	RequestTimeout time.Duration | 	RequestTimeout time.Duration | ||||||
|  | 	// Use the services own auth token | ||||||
|  | 	WithServicePrivileges bool | ||||||
|  |  | ||||||
| 	// Middleware for low level call func | 	// Middleware for low level call func | ||||||
| 	CallWrappers []CallWrapper | 	CallWrappers []CallWrapper | ||||||
| @@ -99,6 +103,7 @@ func NewOptions(options ...Option) Options { | |||||||
| 		}, | 		}, | ||||||
| 		PoolSize:  DefaultPoolSize, | 		PoolSize:  DefaultPoolSize, | ||||||
| 		PoolTTL:   DefaultPoolTTL, | 		PoolTTL:   DefaultPoolTTL, | ||||||
|  | 		Auth:      auth.DefaultAuth, | ||||||
| 		Broker:    broker.DefaultBroker, | 		Broker:    broker.DefaultBroker, | ||||||
| 		Selector:  selector.DefaultSelector, | 		Selector:  selector.DefaultSelector, | ||||||
| 		Registry:  registry.DefaultRegistry, | 		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 | // Codec to be used to encode/decode requests for a given content type | ||||||
| func Codec(contentType string, c codec.NewCodec) Option { | func Codec(contentType string, c codec.NewCodec) Option { | ||||||
| 	return func(o *Options) { | 	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 { | func WithMessageContentType(ct string) MessageOption { | ||||||
| 	return func(o *MessageOptions) { | 	return func(o *MessageOptions) { | ||||||
| 		o.ContentType = ct | 		o.ContentType = ct | ||||||
|   | |||||||
| @@ -255,9 +255,14 @@ var ( | |||||||
| 			Usage:   "Auth for role based access control, e.g. service", | 			Usage:   "Auth for role based access control, e.g. service", | ||||||
| 		}, | 		}, | ||||||
| 		&cli.StringFlag{ | 		&cli.StringFlag{ | ||||||
| 			Name:    "auth_token", | 			Name:    "auth_id", | ||||||
| 			EnvVars: []string{"MICRO_AUTH_TOKEN"}, | 			EnvVars: []string{"MICRO_AUTH_ID"}, | ||||||
| 			Usage:   "Auth token used for client authentication", | 			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{ | 		&cli.StringFlag{ | ||||||
| 			Name:    "auth_public_key", | 			Name:    "auth_public_key", | ||||||
| @@ -488,6 +493,7 @@ func (c *cmd) Before(ctx *cli.Context) error { | |||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		*c.opts.Auth = a() | 		*c.opts.Auth = a() | ||||||
|  | 		clientOpts = append(clientOpts, client.Auth(*c.opts.Auth)) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	// Set the profile | 	// Set the profile | ||||||
| @@ -655,8 +661,10 @@ func (c *cmd) Before(ctx *cli.Context) error { | |||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	if len(ctx.String("auth_token")) > 0 { | 	if len(ctx.String("auth_id")) > 0 || len(ctx.String("auth_secret")) > 0 { | ||||||
| 		authOpts = append(authOpts, auth.ServiceToken(ctx.String("auth_token"))) | 		authOpts = append(authOpts, auth.Credentials( | ||||||
|  | 			ctx.String("auth_id"), ctx.String("auth_secret"), | ||||||
|  | 		)) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	if len(ctx.String("auth_public_key")) > 0 { | 	if len(ctx.String("auth_public_key")) > 0 { | ||||||
|   | |||||||
| @@ -142,6 +142,7 @@ func Tracer(t trace.Tracer) Option { | |||||||
| func Auth(a auth.Auth) Option { | func Auth(a auth.Auth) Option { | ||||||
| 	return func(o *Options) { | 	return func(o *Options) { | ||||||
| 		o.Auth = a | 		o.Auth = a | ||||||
|  | 		o.Client.Init(client.Auth(a)) | ||||||
| 		o.Server.Init(server.Auth(a)) | 		o.Server.Init(server.Auth(a)) | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|   | |||||||
| @@ -18,7 +18,6 @@ import ( | |||||||
| 	"github.com/micro/go-micro/v2/plugin" | 	"github.com/micro/go-micro/v2/plugin" | ||||||
| 	"github.com/micro/go-micro/v2/server" | 	"github.com/micro/go-micro/v2/server" | ||||||
| 	"github.com/micro/go-micro/v2/store" | 	"github.com/micro/go-micro/v2/store" | ||||||
| 	"github.com/micro/go-micro/v2/util/config" |  | ||||||
| 	"github.com/micro/go-micro/v2/util/wrapper" | 	"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 | 		// Right now we're just going to load a token | ||||||
| 		// May need to re-read value on change | 		// May need to re-read value on change | ||||||
| 		// TODO: should be scoped to micro/auth/token | 		// TODO: should be scoped to micro/auth/token | ||||||
| 		if tk, _ := config.Get("token"); len(tk) > 0 { | 		// if tk, _ := config.Get("token"); len(tk) > 0 { | ||||||
| 			s.opts.Auth.Init(auth.ServiceToken(tk)) | 		// 	s.opts.Auth.Init(auth.ServiceToken(tk)) | ||||||
| 		} | 		// } | ||||||
| 	}) | 	}) | ||||||
| } | } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -38,15 +38,6 @@ func (c *clientWrapper) setHeaders(ctx context.Context) context.Context { | |||||||
| 	mda, _ := metadata.FromContext(ctx) | 	mda, _ := metadata.FromContext(ctx) | ||||||
| 	md := metadata.Copy(mda) | 	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 | 	// set headers | ||||||
| 	for k, v := range c.headers { | 	for k, v := range c.headers { | ||||||
| 		if _, ok := md[k]; !ok { | 		if _, ok := md[k]; !ok { | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user