add MICRO_AUTH_TOKEN, parse token in wrapper, preload config and othe… (#1261)

* add MICRO_AUTH_TOKEN, parse token in wrapper, preload config and other things

* fix wrapper panic
This commit is contained in:
Asim Aslam 2020-02-25 22:15:44 +00:00 committed by GitHub
parent 603d37b135
commit 6aaaf54275
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 243 additions and 177 deletions

View File

@ -15,8 +15,8 @@ type Auth interface {
Generate(id string, opts ...GenerateOption) (*Account, error) Generate(id string, opts ...GenerateOption) (*Account, error)
// Revoke an authorization Account // Revoke an authorization Account
Revoke(token string) error Revoke(token string) error
// Validate an account token // Verify an account token
Validate(token string) (*Account, error) Verify(token string) (*Account, error)
// String returns the implementation // String returns the implementation
String() string String() string
} }
@ -31,7 +31,10 @@ type Resource struct {
// Role an account has // Role an account has
type Role struct { type Role struct {
Name string // Name of the role
Name string
// The resource it has access
// TODO: potentially remove
Resource *Resource Resource *Resource
} }

View File

@ -6,31 +6,42 @@ var (
// NewAuth returns a new default registry which is noop // NewAuth returns a new default registry which is noop
func NewAuth(opts ...Option) Auth { func NewAuth(opts ...Option) Auth {
return noop{} var options Options
for _, o := range opts {
o(&options)
}
return &noop{
opts: options,
}
} }
type noop struct{} type noop struct {
opts Options
}
func (noop) Init(opts ...Option) error { func (n *noop) Init(opts ...Option) error {
for _, o := range opts {
o(&n.opts)
}
return nil return nil
} }
func (noop) Options() Options { func (n *noop) Options() Options {
return Options{} return n.opts
} }
func (noop) Generate(id string, opts ...GenerateOption) (*Account, error) { func (n *noop) Generate(id string, opts ...GenerateOption) (*Account, error) {
return nil, nil return nil, nil
} }
func (noop) Revoke(token string) error { func (n *noop) Revoke(token string) error {
return nil return nil
} }
func (noop) Validate(token string) (*Account, error) { func (n *noop) Verify(token string) (*Account, error) {
return nil, nil return nil, nil
} }
func (noop) String() string { func (n *noop) String() string {
return "noop" return "noop"
} }

View File

@ -1,6 +1,7 @@
package jwt package jwt
import ( import (
"encoding/base64"
"errors" "errors"
"time" "time"
@ -8,17 +9,19 @@ import (
"github.com/micro/go-micro/v2/auth" "github.com/micro/go-micro/v2/auth"
) )
// ErrInvalidPrivateKey is returned when the service provided an invalid private key var (
var ErrInvalidPrivateKey = errors.New("An invalid private key was provided") // ErrInvalidPrivateKey is returned when the service provided an invalid private key
ErrInvalidPrivateKey = errors.New("An invalid private key was provided")
// ErrEncodingToken is returned when the service encounters an error during encoding // ErrEncodingToken is returned when the service encounters an error during encoding
var ErrEncodingToken = errors.New("An error occured while encoding the JWT") ErrEncodingToken = errors.New("An error occured while encoding the JWT")
// ErrInvalidToken is returned when the token provided is not valid // ErrInvalidToken is returned when the token provided is not valid
var ErrInvalidToken = errors.New("An invalid token was provided") ErrInvalidToken = errors.New("An invalid token was provided")
// ErrMissingToken is returned when no token is provided // ErrMissingToken is returned when no token is provided
var ErrMissingToken = errors.New("A valid JWT is required") ErrMissingToken = errors.New("A valid JWT is required")
)
// NewAuth returns a new instance of the Auth service // NewAuth returns a new instance of the Auth service
func NewAuth(opts ...auth.Option) auth.Auth { func NewAuth(opts ...auth.Option) auth.Auth {
@ -59,7 +62,13 @@ type AuthClaims struct {
// Generate a new JWT // Generate a new JWT
func (s *svc) Generate(id string, ops ...auth.GenerateOption) (*auth.Account, error) { func (s *svc) Generate(id string, ops ...auth.GenerateOption) (*auth.Account, error) {
key, err := jwt.ParseRSAPrivateKeyFromPEM(s.options.PrivateKey) // decode the private key
priv, err := base64.StdEncoding.DecodeString(s.options.PrivateKey)
if err != nil {
return nil, err
}
key, err := jwt.ParseRSAPrivateKeyFromPEM(priv)
if err != nil { if err != nil {
return nil, ErrEncodingToken return nil, ErrEncodingToken
} }
@ -90,14 +99,20 @@ func (s *svc) Revoke(token string) error {
return nil return nil
} }
// Validate a JWT // Verify a JWT
func (s *svc) Validate(token string) (*auth.Account, error) { func (s *svc) Verify(token string) (*auth.Account, error) {
if token == "" { if token == "" {
return nil, ErrMissingToken return nil, ErrMissingToken
} }
// decode the public key
pub, err := base64.StdEncoding.DecodeString(s.options.PublicKey)
if err != nil {
return nil, err
}
res, err := jwt.ParseWithClaims(token, &AuthClaims{}, func(token *jwt.Token) (interface{}, error) { res, err := jwt.ParseWithClaims(token, &AuthClaims{}, func(token *jwt.Token) (interface{}, error) {
return jwt.ParseRSAPublicKeyFromPEM(s.options.PublicKey) return jwt.ParseRSAPublicKeyFromPEM(pub)
}) })
if err != nil { if err != nil {
return nil, err return nil, err

View File

@ -1,41 +1,51 @@
package auth package auth
import (
b64 "encoding/base64"
)
type Options struct { type Options struct {
PublicKey []byte // Token is an auth token
PrivateKey []byte Token string
Excludes []string // Public key base64 encoded
PublicKey string
// Private key base64 encoded
PrivateKey string
// Endpoints to exclude
Exclude []string
} }
type Option func(o *Options) type Option func(o *Options)
// Excludes endpoints from auth // Exclude ecludes a set of endpoints from authorization
func Excludes(excludes ...string) Option { func Exclude(e ...string) Option {
return func(o *Options) { return func(o *Options) {
o.Excludes = excludes o.Exclude = e
} }
} }
// PublicKey is the JWT public key // PublicKey is the JWT public key
func PublicKey(key string) Option { func PublicKey(key string) Option {
return func(o *Options) { return func(o *Options) {
o.PublicKey, _ = b64.StdEncoding.DecodeString(key) o.PublicKey = key
} }
} }
// PrivateKey is the JWT private key // PrivateKey is the JWT private key
func PrivateKey(key string) Option { func PrivateKey(key string) Option {
return func(o *Options) { return func(o *Options) {
o.PrivateKey, _ = b64.StdEncoding.DecodeString(key) o.PrivateKey = key
}
}
// Token sets an auth token
func Token(t string) Option {
return func(o *Options) {
o.Token = t
} }
} }
type GenerateOptions struct { type GenerateOptions struct {
// Metadata associated with the account
Metadata map[string]string Metadata map[string]string
Roles []*Role // Roles/scopes associated with the account
Roles []*Role
} }
type GenerateOption func(o *GenerateOptions) type GenerateOption func(o *GenerateOptions)

View File

@ -1,5 +1,5 @@
// Code generated by protoc-gen-go. DO NOT EDIT. // Code generated by protoc-gen-go. DO NOT EDIT.
// source: auth/service/proto/auth.proto // source: micro/go-micro/auth/service/proto/auth.proto
package go_micro_auth package go_micro_auth
@ -36,7 +36,7 @@ func (m *Account) Reset() { *m = Account{} }
func (m *Account) String() string { return proto.CompactTextString(m) } func (m *Account) String() string { return proto.CompactTextString(m) }
func (*Account) ProtoMessage() {} func (*Account) ProtoMessage() {}
func (*Account) Descriptor() ([]byte, []int) { func (*Account) Descriptor() ([]byte, []int) {
return fileDescriptor_21300bfacc51fc2a, []int{0} return fileDescriptor_de609d4872dacc78, []int{0}
} }
func (m *Account) XXX_Unmarshal(b []byte) error { func (m *Account) XXX_Unmarshal(b []byte) error {
@ -111,7 +111,7 @@ func (m *Role) Reset() { *m = Role{} }
func (m *Role) String() string { return proto.CompactTextString(m) } func (m *Role) String() string { return proto.CompactTextString(m) }
func (*Role) ProtoMessage() {} func (*Role) ProtoMessage() {}
func (*Role) Descriptor() ([]byte, []int) { func (*Role) Descriptor() ([]byte, []int) {
return fileDescriptor_21300bfacc51fc2a, []int{1} return fileDescriptor_de609d4872dacc78, []int{1}
} }
func (m *Role) XXX_Unmarshal(b []byte) error { func (m *Role) XXX_Unmarshal(b []byte) error {
@ -158,7 +158,7 @@ func (m *Resource) Reset() { *m = Resource{} }
func (m *Resource) String() string { return proto.CompactTextString(m) } func (m *Resource) String() string { return proto.CompactTextString(m) }
func (*Resource) ProtoMessage() {} func (*Resource) ProtoMessage() {}
func (*Resource) Descriptor() ([]byte, []int) { func (*Resource) Descriptor() ([]byte, []int) {
return fileDescriptor_21300bfacc51fc2a, []int{2} return fileDescriptor_de609d4872dacc78, []int{2}
} }
func (m *Resource) XXX_Unmarshal(b []byte) error { func (m *Resource) XXX_Unmarshal(b []byte) error {
@ -204,7 +204,7 @@ func (m *GenerateRequest) Reset() { *m = GenerateRequest{} }
func (m *GenerateRequest) String() string { return proto.CompactTextString(m) } func (m *GenerateRequest) String() string { return proto.CompactTextString(m) }
func (*GenerateRequest) ProtoMessage() {} func (*GenerateRequest) ProtoMessage() {}
func (*GenerateRequest) Descriptor() ([]byte, []int) { func (*GenerateRequest) Descriptor() ([]byte, []int) {
return fileDescriptor_21300bfacc51fc2a, []int{3} return fileDescriptor_de609d4872dacc78, []int{3}
} }
func (m *GenerateRequest) XXX_Unmarshal(b []byte) error { func (m *GenerateRequest) XXX_Unmarshal(b []byte) error {
@ -243,7 +243,7 @@ func (m *GenerateResponse) Reset() { *m = GenerateResponse{} }
func (m *GenerateResponse) String() string { return proto.CompactTextString(m) } func (m *GenerateResponse) String() string { return proto.CompactTextString(m) }
func (*GenerateResponse) ProtoMessage() {} func (*GenerateResponse) ProtoMessage() {}
func (*GenerateResponse) Descriptor() ([]byte, []int) { func (*GenerateResponse) Descriptor() ([]byte, []int) {
return fileDescriptor_21300bfacc51fc2a, []int{4} return fileDescriptor_de609d4872dacc78, []int{4}
} }
func (m *GenerateResponse) XXX_Unmarshal(b []byte) error { func (m *GenerateResponse) XXX_Unmarshal(b []byte) error {
@ -271,78 +271,78 @@ func (m *GenerateResponse) GetAccount() *Account {
return nil return nil
} }
type ValidateRequest struct { type VerifyRequest struct {
Token string `protobuf:"bytes,1,opt,name=token,proto3" json:"token,omitempty"` Token string `protobuf:"bytes,1,opt,name=token,proto3" json:"token,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"` XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"` XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"` XXX_sizecache int32 `json:"-"`
} }
func (m *ValidateRequest) Reset() { *m = ValidateRequest{} } func (m *VerifyRequest) Reset() { *m = VerifyRequest{} }
func (m *ValidateRequest) String() string { return proto.CompactTextString(m) } func (m *VerifyRequest) String() string { return proto.CompactTextString(m) }
func (*ValidateRequest) ProtoMessage() {} func (*VerifyRequest) ProtoMessage() {}
func (*ValidateRequest) Descriptor() ([]byte, []int) { func (*VerifyRequest) Descriptor() ([]byte, []int) {
return fileDescriptor_21300bfacc51fc2a, []int{5} return fileDescriptor_de609d4872dacc78, []int{5}
} }
func (m *ValidateRequest) XXX_Unmarshal(b []byte) error { func (m *VerifyRequest) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_ValidateRequest.Unmarshal(m, b) return xxx_messageInfo_VerifyRequest.Unmarshal(m, b)
} }
func (m *ValidateRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { func (m *VerifyRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_ValidateRequest.Marshal(b, m, deterministic) return xxx_messageInfo_VerifyRequest.Marshal(b, m, deterministic)
} }
func (m *ValidateRequest) XXX_Merge(src proto.Message) { func (m *VerifyRequest) XXX_Merge(src proto.Message) {
xxx_messageInfo_ValidateRequest.Merge(m, src) xxx_messageInfo_VerifyRequest.Merge(m, src)
} }
func (m *ValidateRequest) XXX_Size() int { func (m *VerifyRequest) XXX_Size() int {
return xxx_messageInfo_ValidateRequest.Size(m) return xxx_messageInfo_VerifyRequest.Size(m)
} }
func (m *ValidateRequest) XXX_DiscardUnknown() { func (m *VerifyRequest) XXX_DiscardUnknown() {
xxx_messageInfo_ValidateRequest.DiscardUnknown(m) xxx_messageInfo_VerifyRequest.DiscardUnknown(m)
} }
var xxx_messageInfo_ValidateRequest proto.InternalMessageInfo var xxx_messageInfo_VerifyRequest proto.InternalMessageInfo
func (m *ValidateRequest) GetToken() string { func (m *VerifyRequest) GetToken() string {
if m != nil { if m != nil {
return m.Token return m.Token
} }
return "" return ""
} }
type ValidateResponse struct { type VerifyResponse struct {
Account *Account `protobuf:"bytes,1,opt,name=account,proto3" json:"account,omitempty"` Account *Account `protobuf:"bytes,1,opt,name=account,proto3" json:"account,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"` XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"` XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"` XXX_sizecache int32 `json:"-"`
} }
func (m *ValidateResponse) Reset() { *m = ValidateResponse{} } func (m *VerifyResponse) Reset() { *m = VerifyResponse{} }
func (m *ValidateResponse) String() string { return proto.CompactTextString(m) } func (m *VerifyResponse) String() string { return proto.CompactTextString(m) }
func (*ValidateResponse) ProtoMessage() {} func (*VerifyResponse) ProtoMessage() {}
func (*ValidateResponse) Descriptor() ([]byte, []int) { func (*VerifyResponse) Descriptor() ([]byte, []int) {
return fileDescriptor_21300bfacc51fc2a, []int{6} return fileDescriptor_de609d4872dacc78, []int{6}
} }
func (m *ValidateResponse) XXX_Unmarshal(b []byte) error { func (m *VerifyResponse) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_ValidateResponse.Unmarshal(m, b) return xxx_messageInfo_VerifyResponse.Unmarshal(m, b)
} }
func (m *ValidateResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { func (m *VerifyResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_ValidateResponse.Marshal(b, m, deterministic) return xxx_messageInfo_VerifyResponse.Marshal(b, m, deterministic)
} }
func (m *ValidateResponse) XXX_Merge(src proto.Message) { func (m *VerifyResponse) XXX_Merge(src proto.Message) {
xxx_messageInfo_ValidateResponse.Merge(m, src) xxx_messageInfo_VerifyResponse.Merge(m, src)
} }
func (m *ValidateResponse) XXX_Size() int { func (m *VerifyResponse) XXX_Size() int {
return xxx_messageInfo_ValidateResponse.Size(m) return xxx_messageInfo_VerifyResponse.Size(m)
} }
func (m *ValidateResponse) XXX_DiscardUnknown() { func (m *VerifyResponse) XXX_DiscardUnknown() {
xxx_messageInfo_ValidateResponse.DiscardUnknown(m) xxx_messageInfo_VerifyResponse.DiscardUnknown(m)
} }
var xxx_messageInfo_ValidateResponse proto.InternalMessageInfo var xxx_messageInfo_VerifyResponse proto.InternalMessageInfo
func (m *ValidateResponse) GetAccount() *Account { func (m *VerifyResponse) GetAccount() *Account {
if m != nil { if m != nil {
return m.Account return m.Account
} }
@ -360,7 +360,7 @@ func (m *RevokeRequest) Reset() { *m = RevokeRequest{} }
func (m *RevokeRequest) String() string { return proto.CompactTextString(m) } func (m *RevokeRequest) String() string { return proto.CompactTextString(m) }
func (*RevokeRequest) ProtoMessage() {} func (*RevokeRequest) ProtoMessage() {}
func (*RevokeRequest) Descriptor() ([]byte, []int) { func (*RevokeRequest) Descriptor() ([]byte, []int) {
return fileDescriptor_21300bfacc51fc2a, []int{7} return fileDescriptor_de609d4872dacc78, []int{7}
} }
func (m *RevokeRequest) XXX_Unmarshal(b []byte) error { func (m *RevokeRequest) XXX_Unmarshal(b []byte) error {
@ -398,7 +398,7 @@ func (m *RevokeResponse) Reset() { *m = RevokeResponse{} }
func (m *RevokeResponse) String() string { return proto.CompactTextString(m) } func (m *RevokeResponse) String() string { return proto.CompactTextString(m) }
func (*RevokeResponse) ProtoMessage() {} func (*RevokeResponse) ProtoMessage() {}
func (*RevokeResponse) Descriptor() ([]byte, []int) { func (*RevokeResponse) Descriptor() ([]byte, []int) {
return fileDescriptor_21300bfacc51fc2a, []int{8} return fileDescriptor_de609d4872dacc78, []int{8}
} }
func (m *RevokeResponse) XXX_Unmarshal(b []byte) error { func (m *RevokeResponse) XXX_Unmarshal(b []byte) error {
@ -426,41 +426,43 @@ func init() {
proto.RegisterType((*Resource)(nil), "go.micro.auth.Resource") proto.RegisterType((*Resource)(nil), "go.micro.auth.Resource")
proto.RegisterType((*GenerateRequest)(nil), "go.micro.auth.GenerateRequest") proto.RegisterType((*GenerateRequest)(nil), "go.micro.auth.GenerateRequest")
proto.RegisterType((*GenerateResponse)(nil), "go.micro.auth.GenerateResponse") proto.RegisterType((*GenerateResponse)(nil), "go.micro.auth.GenerateResponse")
proto.RegisterType((*ValidateRequest)(nil), "go.micro.auth.ValidateRequest") proto.RegisterType((*VerifyRequest)(nil), "go.micro.auth.VerifyRequest")
proto.RegisterType((*ValidateResponse)(nil), "go.micro.auth.ValidateResponse") proto.RegisterType((*VerifyResponse)(nil), "go.micro.auth.VerifyResponse")
proto.RegisterType((*RevokeRequest)(nil), "go.micro.auth.RevokeRequest") proto.RegisterType((*RevokeRequest)(nil), "go.micro.auth.RevokeRequest")
proto.RegisterType((*RevokeResponse)(nil), "go.micro.auth.RevokeResponse") proto.RegisterType((*RevokeResponse)(nil), "go.micro.auth.RevokeResponse")
} }
func init() { proto.RegisterFile("auth/service/proto/auth.proto", fileDescriptor_21300bfacc51fc2a) } func init() {
proto.RegisterFile("micro/go-micro/auth/service/proto/auth.proto", fileDescriptor_de609d4872dacc78)
var fileDescriptor_21300bfacc51fc2a = []byte{ }
// 429 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x9c, 0x53, 0x4d, 0x6f, 0xd3, 0x40, var fileDescriptor_de609d4872dacc78 = []byte{
0x10, 0xad, 0x3f, 0xe2, 0x98, 0x89, 0xd2, 0x46, 0x03, 0x2a, 0x56, 0x44, 0x21, 0xb2, 0x40, 0x84, // 432 bytes of a gzipped FileDescriptorProto
0x8b, 0x83, 0xdc, 0x0b, 0x82, 0x0b, 0x15, 0xa0, 0x9e, 0x2a, 0xa4, 0x3d, 0x70, 0x5f, 0xec, 0x11, 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xa4, 0x53, 0x4b, 0x6f, 0xd3, 0x40,
0xb5, 0xe2, 0x78, 0xcd, 0x7a, 0x1d, 0xe1, 0xdf, 0xc0, 0x6f, 0xe5, 0x3f, 0x20, 0xaf, 0xbd, 0x69, 0x10, 0xae, 0x1d, 0xe7, 0xc1, 0x44, 0x09, 0xd1, 0x80, 0x8a, 0x15, 0xf1, 0x88, 0x56, 0x20, 0x05,
0xea, 0xb4, 0xaa, 0xd4, 0xdb, 0x7c, 0xbc, 0x79, 0xf3, 0xde, 0x68, 0x17, 0xce, 0x78, 0xad, 0xae, 0x09, 0x1c, 0xe4, 0x5e, 0x10, 0x5c, 0x28, 0x0f, 0xf5, 0x54, 0x21, 0xed, 0x81, 0xfb, 0xe2, 0x0c,
0x57, 0x15, 0xc9, 0x6d, 0x96, 0xd0, 0xaa, 0x94, 0x42, 0x89, 0x55, 0x5b, 0x8a, 0x74, 0x88, 0xd3, 0xad, 0x95, 0xc4, 0x6b, 0xd6, 0xeb, 0x08, 0xff, 0x06, 0x7e, 0x28, 0x7f, 0x03, 0x79, 0xd7, 0x1b,
0x5f, 0x22, 0xda, 0x64, 0x89, 0x14, 0x51, 0x5b, 0x0c, 0xff, 0xda, 0x30, 0xbe, 0x48, 0x12, 0x51, 0x6a, 0xb7, 0xe5, 0x00, 0xb7, 0x79, 0x7c, 0xf3, 0xcd, 0xf7, 0x8d, 0x76, 0xe1, 0xc5, 0x2e, 0x4d,
0x17, 0x0a, 0x8f, 0xc1, 0xce, 0xd2, 0xc0, 0x5a, 0x58, 0xcb, 0x27, 0xcc, 0xce, 0x52, 0x7c, 0x06, 0x94, 0x5c, 0x5d, 0xc8, 0x97, 0x36, 0x10, 0xa5, 0xbe, 0x5c, 0x15, 0xa4, 0xf6, 0x69, 0x42, 0xab,
0x23, 0x25, 0xd6, 0x54, 0x04, 0xb6, 0x2e, 0x75, 0x09, 0x06, 0x30, 0x4e, 0x24, 0x71, 0x45, 0x69, 0x5c, 0x49, 0x6d, 0x4b, 0x91, 0x09, 0x71, 0x72, 0x21, 0x23, 0x83, 0x8b, 0xea, 0x22, 0xfb, 0xe9,
0xe0, 0x2c, 0xac, 0xa5, 0xc3, 0x4c, 0x8a, 0xa7, 0xe0, 0xd1, 0x9f, 0x32, 0x93, 0x4d, 0xe0, 0xea, 0xc3, 0xf0, 0x34, 0x49, 0x64, 0x99, 0x69, 0x9c, 0x82, 0x9f, 0xae, 0x43, 0x6f, 0xe1, 0x2d, 0xef,
0x46, 0x9f, 0xe1, 0x3b, 0x18, 0x49, 0x91, 0x53, 0x15, 0x8c, 0x16, 0xce, 0x72, 0x12, 0x3f, 0x8d, 0x70, 0x3f, 0x5d, 0xe3, 0x7d, 0xe8, 0x6b, 0xb9, 0xa1, 0x2c, 0xf4, 0x4d, 0xc9, 0x26, 0x18, 0xc2,
0x6e, 0x49, 0x88, 0x98, 0xc8, 0x89, 0x75, 0x08, 0xfc, 0x0c, 0xfe, 0x86, 0x14, 0x4f, 0xb9, 0xe2, 0x30, 0x51, 0x24, 0x34, 0xad, 0xc3, 0xde, 0xc2, 0x5b, 0xf6, 0xb8, 0x4b, 0xf1, 0x18, 0x06, 0xf4,
0x81, 0xa7, 0xd1, 0xaf, 0x07, 0xe8, 0x5e, 0x6c, 0x74, 0xd5, 0xc3, 0xbe, 0x15, 0x4a, 0x36, 0x6c, 0x23, 0x4f, 0x55, 0x15, 0x06, 0xa6, 0xd1, 0x64, 0xf8, 0x1c, 0xfa, 0x4a, 0x6e, 0xa9, 0x08, 0xfb,
0x37, 0x35, 0xff, 0x04, 0xd3, 0x5b, 0x2d, 0x9c, 0x81, 0xb3, 0xa6, 0xa6, 0xb7, 0xd5, 0x86, 0xad, 0x8b, 0xde, 0x72, 0x1c, 0xdf, 0x8b, 0x5a, 0x12, 0x22, 0x2e, 0xb7, 0xc4, 0x2d, 0x02, 0xdf, 0xc1,
0xaf, 0x2d, 0xcf, 0x6b, 0x32, 0xbe, 0x74, 0xf2, 0xd1, 0xfe, 0x60, 0x85, 0xdf, 0xc1, 0x6d, 0xd5, 0x68, 0x47, 0x5a, 0xac, 0x85, 0x16, 0xe1, 0xc0, 0xa0, 0x9f, 0x76, 0xd0, 0x8d, 0xd8, 0xe8, 0xbc,
0x20, 0x82, 0x5b, 0xf0, 0x0d, 0xf5, 0x43, 0x3a, 0xc6, 0x73, 0xf0, 0x25, 0x55, 0xa2, 0x96, 0x49, 0x81, 0x7d, 0xca, 0xb4, 0xaa, 0xf8, 0x61, 0x6a, 0xfe, 0x16, 0x26, 0xad, 0x16, 0xce, 0xa0, 0xb7,
0x37, 0x38, 0x89, 0x9f, 0x0f, 0x8d, 0xf4, 0x6d, 0xb6, 0x03, 0x86, 0x31, 0xf8, 0xa6, 0x7a, 0x27, 0xa1, 0xaa, 0xb1, 0x55, 0x87, 0xb5, 0xaf, 0xbd, 0xd8, 0x96, 0xe4, 0x7c, 0x99, 0xe4, 0x8d, 0xff,
0x29, 0x82, 0xab, 0x9a, 0xd2, 0x28, 0xd1, 0x71, 0xf8, 0x05, 0x4e, 0x2e, 0xa9, 0x20, 0xc9, 0x15, 0xda, 0x63, 0x9f, 0x21, 0xa8, 0xd5, 0x20, 0x42, 0x90, 0x89, 0x1d, 0x35, 0x43, 0x26, 0xc6, 0x13,
0x31, 0xfa, 0x5d, 0x53, 0xa5, 0xf0, 0x3d, 0x8c, 0x79, 0xe7, 0x5b, 0x4f, 0x4f, 0xe2, 0xd3, 0xbb, 0x18, 0x29, 0x2a, 0x64, 0xa9, 0x12, 0x3b, 0x38, 0x8e, 0x1f, 0x74, 0x8d, 0x34, 0x6d, 0x7e, 0x00,
0xaf, 0xc2, 0x0c, 0x2c, 0xfc, 0x0a, 0xb3, 0x1b, 0x92, 0xaa, 0x14, 0x45, 0x45, 0x8f, 0x60, 0x79, 0xb2, 0x18, 0x46, 0xae, 0x7a, 0x23, 0x29, 0x42, 0xa0, 0xab, 0xdc, 0x29, 0x31, 0x31, 0xfb, 0x00,
0x0b, 0x27, 0x3f, 0x78, 0x9e, 0xa5, 0x7b, 0x52, 0x76, 0x8f, 0xc2, 0xda, 0x7b, 0x14, 0xed, 0xba, 0x77, 0xcf, 0x28, 0x23, 0x25, 0x34, 0x71, 0xfa, 0x5e, 0x52, 0xa1, 0xf1, 0x15, 0x0c, 0x85, 0xf5,
0x1b, 0xe0, 0xa3, 0xd7, 0xbd, 0x81, 0x29, 0xa3, 0xad, 0x58, 0x3f, 0xb0, 0x6c, 0x06, 0xc7, 0x06, 0x6d, 0xa6, 0xc7, 0xf1, 0xf1, 0xcd, 0x57, 0xe1, 0x0e, 0xc6, 0x3e, 0xc2, 0xec, 0x0f, 0x49, 0x91,
0xd6, 0xad, 0x8a, 0xff, 0x59, 0xe0, 0x5e, 0xd4, 0xea, 0x1a, 0xaf, 0xc0, 0x37, 0xb6, 0xf1, 0xe5, 0xcb, 0xac, 0xa0, 0x7f, 0x60, 0x79, 0x06, 0x93, 0x2f, 0xa4, 0xd2, 0x6f, 0x95, 0x13, 0x72, 0x78,
0x60, 0xdd, 0xe0, 0xa8, 0xf3, 0x57, 0xf7, 0xf6, 0x3b, 0xd6, 0xf0, 0xa8, 0xa5, 0x33, 0xb6, 0x0e, 0x12, 0xde, 0x95, 0x27, 0xc1, 0xde, 0xc3, 0xd4, 0xc1, 0xfe, 0x67, 0x15, 0xa7, 0xbd, 0xdc, 0xd0,
0xe8, 0x06, 0x87, 0x39, 0xa0, 0x1b, 0xde, 0x23, 0x3c, 0xc2, 0x4b, 0xf0, 0x3a, 0xe1, 0xf8, 0xe2, 0xdf, 0x57, 0xcd, 0x60, 0xea, 0x60, 0x76, 0x55, 0xfc, 0xcb, 0x83, 0xe0, 0xb4, 0xd4, 0x97, 0x78,
0xe0, 0xe9, 0xec, 0xd9, 0x9e, 0x9f, 0xdd, 0xd3, 0x35, 0x44, 0x3f, 0x3d, 0xfd, 0x97, 0xcf, 0xff, 0x0e, 0x23, 0x67, 0x19, 0x1f, 0x77, 0xd6, 0x75, 0x0e, 0x3a, 0x7f, 0x72, 0x6b, 0xdf, 0xb2, 0xb2,
0x07, 0x00, 0x00, 0xff, 0xff, 0x79, 0x35, 0xb2, 0x7e, 0xec, 0x03, 0x00, 0x00, 0x23, 0x3c, 0x83, 0x81, 0x35, 0x85, 0x0f, 0x3b, 0xe0, 0xd6, 0x49, 0xe6, 0x8f, 0x6e, 0xe9, 0x5e,
0x25, 0xb2, 0x92, 0xaf, 0x11, 0xb5, 0x0c, 0x5f, 0x23, 0x6a, 0xfb, 0x64, 0x47, 0x5f, 0x07, 0xe6,
0x07, 0x9f, 0xfc, 0x0e, 0x00, 0x00, 0xff, 0xff, 0xf0, 0x34, 0xce, 0x17, 0xf1, 0x03, 0x00, 0x00,
} }

View File

@ -1,16 +1,16 @@
// Code generated by protoc-gen-micro. DO NOT EDIT. // Code generated by protoc-gen-micro. DO NOT EDIT.
// source: auth/service/proto/auth.proto // source: micro/go-micro/auth/service/proto/auth.proto
package go_micro_auth package go_micro_auth
import ( import (
fmt "fmt" fmt "fmt"
math "math"
context "context"
proto "github.com/golang/protobuf/proto" proto "github.com/golang/protobuf/proto"
math "math"
)
import (
context "context"
client "github.com/micro/go-micro/v2/client" client "github.com/micro/go-micro/v2/client"
server "github.com/micro/go-micro/v2/server" server "github.com/micro/go-micro/v2/server"
) )
@ -35,7 +35,7 @@ var _ server.Option
type AuthService interface { type AuthService interface {
Generate(ctx context.Context, in *GenerateRequest, opts ...client.CallOption) (*GenerateResponse, error) Generate(ctx context.Context, in *GenerateRequest, opts ...client.CallOption) (*GenerateResponse, error)
Validate(ctx context.Context, in *ValidateRequest, opts ...client.CallOption) (*ValidateResponse, error) Verify(ctx context.Context, in *VerifyRequest, opts ...client.CallOption) (*VerifyResponse, error)
Revoke(ctx context.Context, in *RevokeRequest, opts ...client.CallOption) (*RevokeResponse, error) Revoke(ctx context.Context, in *RevokeRequest, opts ...client.CallOption) (*RevokeResponse, error)
} }
@ -45,12 +45,6 @@ type authService struct {
} }
func NewAuthService(name string, c client.Client) AuthService { func NewAuthService(name string, c client.Client) AuthService {
if c == nil {
c = client.NewClient()
}
if len(name) == 0 {
name = "go.micro.auth"
}
return &authService{ return &authService{
c: c, c: c,
name: name, name: name,
@ -67,9 +61,9 @@ func (c *authService) Generate(ctx context.Context, in *GenerateRequest, opts ..
return out, nil return out, nil
} }
func (c *authService) Validate(ctx context.Context, in *ValidateRequest, opts ...client.CallOption) (*ValidateResponse, error) { func (c *authService) Verify(ctx context.Context, in *VerifyRequest, opts ...client.CallOption) (*VerifyResponse, error) {
req := c.c.NewRequest(c.name, "Auth.Validate", in) req := c.c.NewRequest(c.name, "Auth.Verify", in)
out := new(ValidateResponse) out := new(VerifyResponse)
err := c.c.Call(ctx, req, out, opts...) err := c.c.Call(ctx, req, out, opts...)
if err != nil { if err != nil {
return nil, err return nil, err
@ -91,14 +85,14 @@ func (c *authService) Revoke(ctx context.Context, in *RevokeRequest, opts ...cli
type AuthHandler interface { type AuthHandler interface {
Generate(context.Context, *GenerateRequest, *GenerateResponse) error Generate(context.Context, *GenerateRequest, *GenerateResponse) error
Validate(context.Context, *ValidateRequest, *ValidateResponse) error Verify(context.Context, *VerifyRequest, *VerifyResponse) error
Revoke(context.Context, *RevokeRequest, *RevokeResponse) error Revoke(context.Context, *RevokeRequest, *RevokeResponse) error
} }
func RegisterAuthHandler(s server.Server, hdlr AuthHandler, opts ...server.HandlerOption) error { func RegisterAuthHandler(s server.Server, hdlr AuthHandler, opts ...server.HandlerOption) error {
type auth interface { type auth interface {
Generate(ctx context.Context, in *GenerateRequest, out *GenerateResponse) error Generate(ctx context.Context, in *GenerateRequest, out *GenerateResponse) error
Validate(ctx context.Context, in *ValidateRequest, out *ValidateResponse) error Verify(ctx context.Context, in *VerifyRequest, out *VerifyResponse) error
Revoke(ctx context.Context, in *RevokeRequest, out *RevokeResponse) error Revoke(ctx context.Context, in *RevokeRequest, out *RevokeResponse) error
} }
type Auth struct { type Auth struct {
@ -116,8 +110,8 @@ func (h *authHandler) Generate(ctx context.Context, in *GenerateRequest, out *Ge
return h.AuthHandler.Generate(ctx, in, out) return h.AuthHandler.Generate(ctx, in, out)
} }
func (h *authHandler) Validate(ctx context.Context, in *ValidateRequest, out *ValidateResponse) error { func (h *authHandler) Verify(ctx context.Context, in *VerifyRequest, out *VerifyResponse) error {
return h.AuthHandler.Validate(ctx, in, out) return h.AuthHandler.Verify(ctx, in, out)
} }
func (h *authHandler) Revoke(ctx context.Context, in *RevokeRequest, out *RevokeResponse) error { func (h *authHandler) Revoke(ctx context.Context, in *RevokeRequest, out *RevokeResponse) error {

View File

@ -4,47 +4,47 @@ package go.micro.auth;
service Auth { service Auth {
rpc Generate(GenerateRequest) returns (GenerateResponse) {}; rpc Generate(GenerateRequest) returns (GenerateResponse) {};
rpc Validate(ValidateRequest) returns (ValidateResponse) {}; rpc Verify(VerifyRequest) returns (VerifyResponse) {};
rpc Revoke(RevokeRequest) returns (RevokeResponse) {}; rpc Revoke(RevokeRequest) returns (RevokeResponse) {};
} }
message Account{ message Account{
string id = 1; string id = 1;
string token = 2; string token = 2;
int64 created = 3; int64 created = 3;
int64 expiry = 4; int64 expiry = 4;
repeated Role roles = 5; repeated Role roles = 5;
map<string, string> metadata = 6; map<string, string> metadata = 6;
} }
message Role { message Role {
string name = 1; string name = 1;
Resource resource = 2; Resource resource = 2;
} }
message Resource{ message Resource{
string name = 1; string name = 1;
string type = 2; string type = 2;
} }
message GenerateRequest { message GenerateRequest {
Account account = 1; Account account = 1;
} }
message GenerateResponse { message GenerateResponse {
Account account = 1; Account account = 1;
} }
message ValidateRequest { message VerifyRequest {
string token = 1; string token = 1;
} }
message ValidateResponse { message VerifyResponse {
Account account = 1; Account account = 1;
} }
message RevokeRequest { message RevokeRequest {
string token = 1; string token = 1;
} }
message RevokeResponse {} message RevokeResponse {}

View File

@ -72,9 +72,9 @@ func (s *svc) Revoke(token string) error {
return err return err
} }
// Validate an account token // Verify an account token
func (s *svc) Validate(token string) (*auth.Account, error) { func (s *svc) Verify(token string) (*auth.Account, error) {
resp, err := s.auth.Validate(context.Background(), &pb.ValidateRequest{Token: token}) resp, err := s.auth.Verify(context.Background(), &pb.VerifyRequest{Token: token})
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@ -7,14 +7,19 @@ import (
"github.com/google/uuid" "github.com/google/uuid"
"github.com/micro/go-micro/v2/auth" "github.com/micro/go-micro/v2/auth"
"github.com/micro/go-micro/v2/errors" "github.com/micro/go-micro/v2/errors"
"github.com/micro/go-micro/v2/store" "github.com/micro/go-micro/v2/store"
) )
type Auth struct {
store store.Store
opts auth.Options
}
// NewAuth returns an instance of store auth // NewAuth returns an instance of store auth
func NewAuth(opts ...auth.Option) auth.Auth { func NewAuth(opts ...auth.Option) auth.Auth {
options := auth.Options{} var options auth.Options
for _, o := range opts { for _, o := range opts {
o(&options) o(&options)
} }
@ -25,11 +30,6 @@ func NewAuth(opts ...auth.Option) auth.Auth {
} }
} }
type Auth struct {
store store.Store
opts auth.Options
}
// Init the auth package // Init the auth package
func (a *Auth) Init(opts ...auth.Option) error { func (a *Auth) Init(opts ...auth.Option) error {
for _, o := range opts { for _, o := range opts {
@ -64,6 +64,7 @@ func (a *Auth) Generate(id string, opts ...auth.GenerateOption) (*auth.Account,
} }
// encode the data to bytes // encode the data to bytes
// TODO: replace with json
buf := &bytes.Buffer{} buf := &bytes.Buffer{}
e := gob.NewEncoder(buf) e := gob.NewEncoder(buf)
if err := e.Encode(sa); err != nil { if err := e.Encode(sa); err != nil {
@ -102,8 +103,8 @@ func (a *Auth) Revoke(token string) error {
return nil return nil
} }
// Validate an account token // Verify an account token
func (a *Auth) Validate(token string) (*auth.Account, error) { func (a *Auth) Verify(token string) (*auth.Account, error) {
// lookup the record by token // lookup the record by token
records, err := a.store.Read(token, store.ReadSuffix()) records, err := a.store.Read(token, store.ReadSuffix())
if err == store.ErrNotFound || len(records) == 0 { if err == store.ErrNotFound || len(records) == 0 {
@ -113,6 +114,7 @@ func (a *Auth) Validate(token string) (*auth.Account, error) {
} }
// decode the result // decode the result
// TODO: replace with json
b := bytes.NewBuffer(records[0].Value) b := bytes.NewBuffer(records[0].Value)
decoder := gob.NewDecoder(b) decoder := gob.NewDecoder(b)
var sa auth.Account var sa auth.Account

View File

@ -18,7 +18,6 @@ import (
"github.com/micro/go-micro/v2/errors" "github.com/micro/go-micro/v2/errors"
"github.com/micro/go-micro/v2/metadata" "github.com/micro/go-micro/v2/metadata"
"github.com/micro/go-micro/v2/registry" "github.com/micro/go-micro/v2/registry"
"github.com/micro/go-micro/v2/util/config"
"google.golang.org/grpc" "google.golang.org/grpc"
"google.golang.org/grpc/credentials" "google.golang.org/grpc/credentials"
@ -129,10 +128,6 @@ func (g *grpcClient) call(ctx context.Context, node *registry.Node, req client.R
header["timeout"] = fmt.Sprintf("%d", opts.RequestTimeout) header["timeout"] = fmt.Sprintf("%d", opts.RequestTimeout)
// 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 token, err := config.Get("token"); err == nil && len(token) > 0 {
header["authorization"] = fmt.Sprintf("Bearer %v", token)
}
md := gmetadata.New(header) md := gmetadata.New(header)
ctx = gmetadata.NewOutgoingContext(ctx, md) ctx = gmetadata.NewOutgoingContext(ctx, md)

View File

@ -249,6 +249,11 @@ var (
EnvVars: []string{"MICRO_AUTH"}, EnvVars: []string{"MICRO_AUTH"},
Usage: "Auth for role based access control, e.g. service", 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",
},
&cli.StringFlag{ &cli.StringFlag{
Name: "auth_public_key", Name: "auth_public_key",
EnvVars: []string{"MICRO_AUTH_PUBLIC_KEY"}, EnvVars: []string{"MICRO_AUTH_PUBLIC_KEY"},
@ -606,6 +611,10 @@ func (c *cmd) Before(ctx *cli.Context) error {
} }
} }
if len(ctx.String("auth_token")) > 0 {
authOpts = append(authOpts, auth.Token(ctx.String("auth_token")))
}
if len(ctx.String("auth_public_key")) > 0 { if len(ctx.String("auth_public_key")) > 0 {
authOpts = append(authOpts, auth.PublicKey(ctx.String("auth_public_key"))) authOpts = append(authOpts, auth.PublicKey(ctx.String("auth_public_key")))
} }
@ -615,7 +624,7 @@ func (c *cmd) Before(ctx *cli.Context) error {
} }
if len(ctx.StringSlice("auth_exclude")) > 0 { if len(ctx.StringSlice("auth_exclude")) > 0 {
authOpts = append(authOpts, auth.Excludes(ctx.StringSlice("auth_exclude")...)) authOpts = append(authOpts, auth.Exclude(ctx.StringSlice("auth_exclude")...))
} }
if len(authOpts) > 0 { if len(authOpts) > 0 {

View File

@ -17,6 +17,7 @@ import (
log "github.com/micro/go-micro/v2/logger" log "github.com/micro/go-micro/v2/logger"
"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/util/config"
"github.com/micro/go-micro/v2/util/wrapper" "github.com/micro/go-micro/v2/util/wrapper"
) )
@ -37,7 +38,7 @@ func newService(opts ...Option) Service {
authFn := func() auth.Auth { return service.opts.Auth } authFn := func() auth.Auth { return service.opts.Auth }
// wrap client to inject From-Service header on any calls // wrap client to inject From-Service header on any calls
options.Client = wrapper.FromService(serviceName, options.Client) options.Client = wrapper.FromService(serviceName, options.Client, authFn)
options.Client = wrapper.TraceCall(serviceName, trace.DefaultTracer, options.Client) options.Client = wrapper.TraceCall(serviceName, trace.DefaultTracer, options.Client)
// wrap the server to provide handler stats // wrap the server to provide handler stats
@ -102,6 +103,14 @@ func (s *service) Init(opts ...Option) {
); err != nil { ); err != nil {
log.Fatal(err) log.Fatal(err)
} }
// TODO: replace Cmd.Init with config.Load
// 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.Token(tk))
}
}) })
} }

View File

@ -15,6 +15,10 @@ import (
type clientWrapper struct { type clientWrapper struct {
client.Client client.Client
// Auth interface
auth func() auth.Auth
// headers to inject
headers metadata.Metadata headers metadata.Metadata
} }
@ -27,7 +31,7 @@ type traceWrapper struct {
var ( var (
HeaderPrefix = "Micro-" HeaderPrefix = "Micro-"
BearerSchema = "Bearer " BearerScheme = "Bearer "
) )
func (c *clientWrapper) setHeaders(ctx context.Context) context.Context { func (c *clientWrapper) setHeaders(ctx context.Context) context.Context {
@ -35,6 +39,15 @@ 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"] = 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 {
@ -75,10 +88,11 @@ func (c *traceWrapper) Call(ctx context.Context, req client.Request, rsp interfa
return err return err
} }
// FromService wraps a client to inject From-Service header into metadata // FromService wraps a client to inject service and auth metadata
func FromService(name string, c client.Client) client.Client { func FromService(name string, c client.Client, fn func() auth.Auth) client.Client {
return &clientWrapper{ return &clientWrapper{
c, c,
fn,
metadata.Metadata{ metadata.Metadata{
HeaderPrefix + "From-Service": name, HeaderPrefix + "From-Service": name,
}, },
@ -151,7 +165,7 @@ func AuthHandler(fn func() auth.Auth) server.HandlerWrapper {
} }
// Exclude any user excluded endpoints // Exclude any user excluded endpoints
for _, e := range a.Options().Excludes { for _, e := range a.Options().Exclude {
if e == req.Endpoint() { if e == req.Endpoint() {
return h(ctx, req, rsp) return h(ctx, req, rsp)
} }
@ -162,15 +176,15 @@ func AuthHandler(fn func() auth.Auth) server.HandlerWrapper {
var token string var token string
if header, ok := metadata.Get(ctx, "Authorization"); ok { if header, ok := metadata.Get(ctx, "Authorization"); ok {
// Ensure the correct scheme is being used // Ensure the correct scheme is being used
if !strings.HasPrefix(header, BearerSchema) { if !strings.HasPrefix(header, BearerScheme) {
return errors.Unauthorized("go.micro.auth", "invalid authorization header. expected Bearer schema") return errors.Unauthorized("go.micro.auth", "invalid authorization header. expected Bearer schema")
} }
token = header[len(BearerSchema):] token = header[len(BearerScheme):]
} }
// Validate the token // Verify the token
if _, err := a.Validate(token); err != nil { if _, err := a.Verify(token); err != nil {
return errors.Unauthorized("go.micro.auth", err.Error()) return errors.Unauthorized("go.micro.auth", err.Error())
} }

View File

@ -4,6 +4,7 @@ import (
"context" "context"
"testing" "testing"
"github.com/micro/go-micro/v2/auth"
"github.com/micro/go-micro/v2/metadata" "github.com/micro/go-micro/v2/metadata"
) )
@ -33,6 +34,7 @@ func TestWrapper(t *testing.T) {
for _, d := range testData { for _, d := range testData {
c := &clientWrapper{ c := &clientWrapper{
auth: func() auth.Auth { return nil },
headers: d.headers, headers: d.headers,
} }