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:
ben-toogood 2020-03-31 16:18:04 +01:00 committed by GitHub
commit 2674790694
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 114 additions and 31 deletions

View File

@ -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
}
}

View File

@ -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,

View File

@ -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
}
}

View File

@ -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

View File

@ -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 {

View File

@ -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))
}
}

View File

@ -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))
// }
})
}

View File

@ -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)

View File

@ -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 {