Service => Service Auth (#1448)
* Service => Service Auth * WithServicePrivileges => ServicePrivileges * Fixes for CLI login * ServicePrivileges => ServiceToken * Fallback to service token Co-authored-by: Ben Toogood <ben@micro.mu>
This commit is contained in:
commit
2674790694
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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,
|
||||
|
@ -131,9 +131,20 @@ 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 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,
|
||||
// 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
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
ServiceToken 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 {
|
||||
}
|
||||
}
|
||||
|
||||
// WithServiceToken is a CallOption which overrides the
|
||||
// authorization header with the services own auth token
|
||||
func WithServiceToken() CallOption {
|
||||
return func(o *CallOptions) {
|
||||
o.ServiceToken = true
|
||||
}
|
||||
}
|
||||
|
||||
func WithMessageContentType(ct string) MessageOption {
|
||||
return func(o *MessageOptions) {
|
||||
o.ContentType = ct
|
||||
|
@ -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 {
|
||||
|
@ -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))
|
||||
}
|
||||
}
|
||||
|
@ -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))
|
||||
// }
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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 {
|
||||
|
Loading…
Reference in New Issue
Block a user