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 {
|
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,9 +131,20 @@ 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 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 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
|
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
|
||||||
|
ServiceToken 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 {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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 {
|
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))
|
||||||
}
|
// }
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -21,13 +21,13 @@ const FileName = ".micro"
|
|||||||
var config = newConfig()
|
var config = newConfig()
|
||||||
|
|
||||||
// Get a value from the .micro file
|
// Get a value from the .micro file
|
||||||
func Get(key string) (string, error) {
|
func Get(path ...string) (string, error) {
|
||||||
tk := config.Get(key).String("")
|
tk := config.Get(path...).String("")
|
||||||
return strings.TrimSpace(tk), nil
|
return strings.TrimSpace(tk), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set a value in the .micro file
|
// Set a value in the .micro file
|
||||||
func Set(key, value string) error {
|
func Set(value string, path ...string) error {
|
||||||
// get the filepath
|
// get the filepath
|
||||||
fp, err := filePath()
|
fp, err := filePath()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -35,7 +35,7 @@ func Set(key, value string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// set the value
|
// set the value
|
||||||
config.Set(value, key)
|
config.Set(value, path...)
|
||||||
|
|
||||||
// write to the file
|
// write to the file
|
||||||
return ioutil.WriteFile(fp, config.Bytes(), 0644)
|
return ioutil.WriteFile(fp, config.Bytes(), 0644)
|
||||||
|
@ -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 {
|
||||||
|
Loading…
Reference in New Issue
Block a user