diff --git a/api/server/auth/auth.go b/api/server/auth/auth.go index e89f0233..d7018f4c 100644 --- a/api/server/auth/auth.go +++ b/api/server/auth/auth.go @@ -43,44 +43,42 @@ func (h authHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) { } else { // Get the token out the cookies if not provided in headers if c, err := req.Cookie("micro-token"); err == nil && c != nil { - token = strings.TrimPrefix(c.Value, auth.CookieName+"=") + token = strings.TrimPrefix(c.Value, auth.TokenCookieName+"=") req.Header.Set("Authorization", BearerScheme+token) } } - // Return if the user disabled auth on this endpoint - excludes := h.auth.Options().Exclude - excludes = append(excludes, DefaultExcludes...) - - loginURL := h.auth.Options().LoginURL - if len(loginURL) > 0 { - excludes = append(excludes, loginURL) + // Get the account using the token, fallback to a blank account + // since some endpoints can be unauthenticated, so the lack of an + // account doesn't necesserially mean a forbidden request + acc, err := h.auth.Inspect(token) + if err != nil { + acc = &auth.Account{} } + err = h.auth.Verify(acc, &auth.Resource{ + Type: "service", + Name: "go.micro.web", + Endpoint: req.URL.Path, + }) - for _, e := range excludes { - // is a standard exclude, e.g. /rpc - if e == req.URL.Path { - h.handler.ServeHTTP(w, req) - return - } - - // is a wildcard exclude, e.g. /services/* - wildcard := strings.Replace(e, "*", "", 1) - if strings.HasSuffix(e, "*") && strings.HasPrefix(req.URL.Path, wildcard) { - h.handler.ServeHTTP(w, req) - return - } - } - - // If the token is valid, allow the request - if _, err := h.auth.Verify(token); err == nil { + // The account has the necessary permissions to access the + // resource + if err == nil { h.handler.ServeHTTP(w, req) return } + // The account is set, but they don't have enough permissions, + // hence we 403. + if len(acc.ID) > 0 { + w.WriteHeader(http.StatusForbidden) + return + } + // If there is no auth login url set, 401 + loginURL := h.auth.Options().LoginURL if loginURL == "" { - w.WriteHeader(401) + w.WriteHeader(http.StatusUnauthorized) return } diff --git a/auth/auth.go b/auth/auth.go index 36ee3f4c..5c5d7ba1 100644 --- a/auth/auth.go +++ b/auth/auth.go @@ -4,24 +4,44 @@ package auth import ( "context" "encoding/json" + "errors" "time" "github.com/micro/go-micro/v2/metadata" ) +var ( + // ErrNotFound is returned when a resouce cannot be found + ErrNotFound = errors.New("not found") + // ErrEncodingToken is returned when the service encounters an error during encoding + ErrEncodingToken = errors.New("error encoding the token") + // ErrInvalidToken is returned when the token provided is not valid + ErrInvalidToken = errors.New("invalid token provided") + // ErrInvalidRole is returned when the role provided was invalid + ErrInvalidRole = errors.New("invalid role") + // ErrForbidden is returned when a user does not have the necessary roles to access a resource + ErrForbidden = errors.New("resource forbidden") +) + // Auth providers authentication and authorization type Auth interface { - // Init the auth package - Init(opts ...Option) error - // Options returns the options set + // Init the auth + Init(opts ...Option) + // Options set for auth Options() Options - // Generate a new auth Account + // Generate a new account Generate(id string, opts ...GenerateOption) (*Account, error) - // Revoke an authorization Account - Revoke(token string) error - // Verify an account token - Verify(token string) (*Account, error) - // String returns the implementation + // Grant access to a resource + Grant(role string, res *Resource) error + // Revoke access to a resource + Revoke(role string, res *Resource) error + // Verify an account has access to a resource + Verify(acc *Account, res *Resource) error + // Inspect a token + Inspect(token string) (*Account, error) + // Refresh an account using a secret + Refresh(secret string, opts ...RefreshOption) (*Token, error) + // String returns the name of the implementation String() string } @@ -31,40 +51,47 @@ type Resource struct { Name string // Type of resource, e.g. Type string -} - -// Role an account has -type Role struct { - // Name of the role - Name string - // The resource it has access - // TODO: potentially remove - Resource *Resource + // Endpoint resource e.g NotesService.Create + Endpoint string } // Account provided by an auth provider type Account struct { // ID of the account (UUIDV4, email or username) - Id string `json:"id"` - // Token used to authenticate - Token string `json:"token"` - // Time of Account creation - Created time.Time `json:"created"` - // Time of Account expiry - Expiry time.Time `json:"expiry"` + ID string `json:"id"` + // Secret used to renew the account + Secret *Token `json:"secret"` // Roles associated with the Account - Roles []*Role `json:"roles"` + Roles []string `json:"roles"` // Any other associated metadata Metadata map[string]string `json:"metadata"` } +// Token can be short or long lived +type Token struct { + // The token itself + Token string `json:"token"` + // Type of token, e.g. JWT + Type string `json:"type"` + // Time of token creation + Created time.Time `json:"created"` + // Time of token expiry + Expiry time.Time `json:"expiry"` + // Subject of the token, e.g. the account ID + Subject string `json:"subject"` + // Roles granted to the token + Roles []string `json:"roles"` + // Metadata embedded in the token + Metadata map[string]string `json:"metadata"` +} + const ( - // MetadataKey is the key used when storing the account - // in metadata + // MetadataKey is the key used when storing the account in metadata MetadataKey = "auth-account" - // CookieName is the name of the cookie which stores the - // auth token - CookieName = "micro-token" + // TokenCookieName is the name of the cookie which stores the auth token + TokenCookieName = "micro-token" + // SecretCookieName is the name of the cookie which stores the auth secret + SecretCookieName = "micro-secret" ) // AccountFromContext gets the account from the context, which diff --git a/auth/default.go b/auth/default.go index 740437f1..15bb1757 100644 --- a/auth/default.go +++ b/auth/default.go @@ -1,122 +1,70 @@ package auth import ( - "encoding/base32" - "sync" - "time" + "github.com/google/uuid" ) var ( DefaultAuth = NewAuth() ) -func genAccount(id string) *Account { - // return a pseudo account - return &Account{ - Id: id, - Token: base32.StdEncoding.EncodeToString([]byte(id)), - Created: time.Now(), - Expiry: time.Now().Add(time.Hour * 24), - Metadata: make(map[string]string), - } -} - -// NewAuth returns a new default registry which is memory func NewAuth(opts ...Option) Auth { - var options Options - for _, o := range opts { - o(&options) - } - - return &memory{ - accounts: make(map[string]*Account), - opts: options, - } + return &noop{} } -// TODO: replace with https://github.com/nats-io/nkeys -// We'll then register public key in registry to use -type memory struct { +type noop struct { opts Options - // accounts - sync.RWMutex - accounts map[string]*Account } -func (n *memory) Init(opts ...Option) error { +// String returns the name of the implementation +func (n *noop) String() string { + return "noop" +} + +// Init the auth +func (n *noop) Init(opts ...Option) { for _, o := range opts { o(&n.opts) } - return nil } -func (n *memory) Options() Options { +// Options set for auth +func (n *noop) Options() Options { return n.opts } -func (n *memory) Generate(id string, opts ...GenerateOption) (*Account, error) { - var options GenerateOptions - for _, o := range opts { - o(&options) - } +// Generate a new account +func (n *noop) Generate(id string, opts ...GenerateOption) (*Account, error) { + options := NewGenerateOptions(opts...) - // return a pseudo account - acc := genAccount(id) - - // set opts - if len(options.Roles) > 0 { - acc.Roles = options.Roles - } - if options.Metadata != nil { - acc.Metadata = options.Metadata - } - - // TODO: don't overwrite - n.Lock() - // maybe save by account id? - n.accounts[acc.Token] = acc - n.Unlock() - - return acc, nil -} - -func (n *memory) Revoke(token string) error { - n.Lock() - delete(n.accounts, token) - n.Unlock() - return nil -} - -func (n *memory) Verify(token string) (*Account, error) { - n.RLock() - defer n.RUnlock() - - if len(token) == 0 { - // pseudo account? - return genAccount(""), nil - } - - // try get the local account if it exists - if acc, ok := n.accounts[token]; ok { - return acc, nil - } - - // decode the token otherwise - b, err := base32.StdEncoding.DecodeString(token) - if err != nil { - return genAccount(""), nil - } - - // return a pseudo account based on token/id return &Account{ - Id: string(b), - Token: token, - Created: time.Now(), - Expiry: time.Now().Add(time.Hour * 24), - Metadata: make(map[string]string), + ID: id, + Roles: options.Roles, + Metadata: options.Metadata, }, nil } -func (n *memory) String() string { - return "memory" +// Grant access to a resource +func (n *noop) Grant(role string, res *Resource) error { + return nil +} + +// Revoke access to a resource +func (n *noop) Revoke(role string, res *Resource) error { + return nil +} + +// Verify an account has access to a resource +func (n *noop) Verify(acc *Account, res *Resource) error { + return nil +} + +// Inspect a token +func (n *noop) Inspect(token string) (*Account, error) { + return &Account{ID: uuid.New().String()}, nil +} + +// Refresh an account using a secret +func (n *noop) Refresh(secret string, opts ...RefreshOption) (*Token, error) { + return &Token{}, nil } diff --git a/auth/jwt/jwt.go b/auth/jwt/jwt.go deleted file mode 100644 index 4db68d61..00000000 --- a/auth/jwt/jwt.go +++ /dev/null @@ -1,133 +0,0 @@ -package jwt - -import ( - "encoding/base64" - "errors" - - "github.com/dgrijalva/jwt-go" - "github.com/micro/go-micro/v2/auth" -) - -var ( - // 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 = errors.New("An error occured while encoding the JWT") - - // ErrInvalidToken is returned when the token provided is not valid - ErrInvalidToken = errors.New("An invalid token was provided") - - // ErrMissingToken is returned when no token is provided - ErrMissingToken = errors.New("A valid JWT is required") -) - -// NewAuth returns a new instance of the Auth service -func NewAuth(opts ...auth.Option) auth.Auth { - svc := new(svc) - svc.Init(opts...) - return svc -} - -// svc is the JWT implementation of the Auth interface -type svc struct { - options auth.Options -} - -func (s *svc) String() string { - return "jwt" -} - -func (s *svc) Options() auth.Options { - return s.options -} - -func (s *svc) Init(opts ...auth.Option) error { - for _, o := range opts { - o(&s.options) - } - - return nil -} - -// AuthClaims to be encoded in the JWT -type AuthClaims struct { - Roles []*auth.Role `json:"roles"` - Metadata map[string]string `json:"metadata"` - - jwt.StandardClaims -} - -// Generate a new JWT -func (s *svc) Generate(id string, ops ...auth.GenerateOption) (*auth.Account, error) { - // 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 { - return nil, ErrEncodingToken - } - - options := auth.NewGenerateOptions(ops...) - account := jwt.NewWithClaims(jwt.SigningMethodRS256, AuthClaims{ - options.Roles, options.Metadata, jwt.StandardClaims{ - Subject: id, - ExpiresAt: options.Expiry.Unix(), - }, - }) - - token, err := account.SignedString(key) - if err != nil { - return nil, err - } - - return &auth.Account{ - Id: id, - Token: token, - Roles: options.Roles, - Metadata: options.Metadata, - }, nil -} - -// Revoke an authorization account -func (s *svc) Revoke(token string) error { - return nil -} - -// Verify a JWT -func (s *svc) Verify(token string) (*auth.Account, error) { - if token == "" { - 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) { - return jwt.ParseRSAPublicKeyFromPEM(pub) - }) - if err != nil { - return nil, err - } - - if !res.Valid { - return nil, ErrInvalidToken - } - - claims, ok := res.Claims.(*AuthClaims) - if !ok { - return nil, ErrInvalidToken - } - - return &auth.Account{ - Id: claims.Subject, - Metadata: claims.Metadata, - Roles: claims.Roles, - }, nil -} diff --git a/auth/options.go b/auth/options.go index 68b357b7..6a49af2e 100644 --- a/auth/options.go +++ b/auth/options.go @@ -4,6 +4,7 @@ import ( "time" "github.com/micro/go-micro/v2/auth/provider" + "github.com/micro/go-micro/v2/store" ) type Options struct { @@ -13,20 +14,20 @@ type Options struct { PublicKey string // Private key base64 encoded PrivateKey string - // Endpoints to exclude - Exclude []string // Provider is an auth provider Provider provider.Provider // LoginURL is the relative url path where a user can login LoginURL string + // Store to back auth + Store store.Store } type Option func(o *Options) -// Exclude ecludes a set of endpoints from authorization -func Exclude(e ...string) Option { +// Store to back auth +func Store(s store.Store) Option { return func(o *Options) { - o.Exclude = e + o.Store = s } } @@ -44,8 +45,8 @@ func PrivateKey(key string) Option { } } -// Token sets an auth token -func Token(t string) Option { +// ServiceToken sets an auth token +func ServiceToken(t string) Option { return func(o *Options) { o.Token = t } @@ -69,31 +70,31 @@ type GenerateOptions struct { // Metadata associated with the account Metadata map[string]string // Roles/scopes associated with the account - Roles []*Role - //Expiry of the token - Expiry time.Time + Roles []string + // SecretExpiry is the time the secret should live for + SecretExpiry time.Duration } type GenerateOption func(o *GenerateOptions) -// Metadata for the generated account -func Metadata(md map[string]string) func(o *GenerateOptions) { +// WithMetadata for the generated account +func WithMetadata(md map[string]string) GenerateOption { return func(o *GenerateOptions) { o.Metadata = md } } -// Roles for the generated account -func Roles(rs []*Role) func(o *GenerateOptions) { +// WithRoles for the generated account +func WithRoles(rs []string) GenerateOption { return func(o *GenerateOptions) { o.Roles = rs } } -// Expiry for the generated account's token expires -func Expiry(ex time.Time) func(o *GenerateOptions) { +// WithSecretExpiry for the generated account's secret expires +func WithSecretExpiry(ex time.Duration) GenerateOption { return func(o *GenerateOptions) { - o.Expiry = ex + o.SecretExpiry = ex } } @@ -103,9 +104,40 @@ func NewGenerateOptions(opts ...GenerateOption) GenerateOptions { for _, o := range opts { o(&options) } - //set defualt expiry of token - if options.Expiry.IsZero() { - options.Expiry = time.Now().Add(time.Hour * 24) + + // set defualt expiry of secret + if options.SecretExpiry == 0 { + options.SecretExpiry = time.Hour * 24 * 7 } + + return options +} + +type RefreshOptions struct { + // TokenExpiry is the time the token should live for + TokenExpiry time.Duration +} + +type RefreshOption func(o *RefreshOptions) + +// WithTokenExpiry for the token +func WithTokenExpiry(ex time.Duration) RefreshOption { + return func(o *RefreshOptions) { + o.TokenExpiry = ex + } +} + +// NewRefreshOptions from a slice of options +func NewRefreshOptions(opts ...RefreshOption) RefreshOptions { + var options RefreshOptions + for _, o := range opts { + o(&options) + } + + // set defualt expiry of token + if options.TokenExpiry == 0 { + options.TokenExpiry = time.Minute + } + return options } diff --git a/auth/service/proto/auth.pb.go b/auth/service/proto/auth.pb.go index b57f3cc5..b06f9b7c 100644 --- a/auth/service/proto/auth.pb.go +++ b/auth/service/proto/auth.pb.go @@ -1,5 +1,5 @@ // Code generated by protoc-gen-go. DO NOT EDIT. -// source: micro/go-micro/auth/service/proto/auth.proto +// source: auth/service/proto/auth.proto package go_micro_auth @@ -20,13 +20,98 @@ var _ = math.Inf // proto package needs to be updated. const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package -type Account struct { - Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` - Token string `protobuf:"bytes,2,opt,name=token,proto3" json:"token,omitempty"` +type Token struct { + Token string `protobuf:"bytes,1,opt,name=token,proto3" json:"token,omitempty"` + Type string `protobuf:"bytes,2,opt,name=type,proto3" json:"type,omitempty"` Created int64 `protobuf:"varint,3,opt,name=created,proto3" json:"created,omitempty"` Expiry int64 `protobuf:"varint,4,opt,name=expiry,proto3" json:"expiry,omitempty"` - Roles []*Role `protobuf:"bytes,5,rep,name=roles,proto3" json:"roles,omitempty"` - Metadata map[string]string `protobuf:"bytes,6,rep,name=metadata,proto3" json:"metadata,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` + Subject string `protobuf:"bytes,5,opt,name=subject,proto3" json:"subject,omitempty"` + Roles []string `protobuf:"bytes,6,rep,name=roles,proto3" json:"roles,omitempty"` + Metadata map[string]string `protobuf:"bytes,7,rep,name=metadata,proto3" json:"metadata,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *Token) Reset() { *m = Token{} } +func (m *Token) String() string { return proto.CompactTextString(m) } +func (*Token) ProtoMessage() {} +func (*Token) Descriptor() ([]byte, []int) { + return fileDescriptor_21300bfacc51fc2a, []int{0} +} + +func (m *Token) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_Token.Unmarshal(m, b) +} +func (m *Token) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_Token.Marshal(b, m, deterministic) +} +func (m *Token) XXX_Merge(src proto.Message) { + xxx_messageInfo_Token.Merge(m, src) +} +func (m *Token) XXX_Size() int { + return xxx_messageInfo_Token.Size(m) +} +func (m *Token) XXX_DiscardUnknown() { + xxx_messageInfo_Token.DiscardUnknown(m) +} + +var xxx_messageInfo_Token proto.InternalMessageInfo + +func (m *Token) GetToken() string { + if m != nil { + return m.Token + } + return "" +} + +func (m *Token) GetType() string { + if m != nil { + return m.Type + } + return "" +} + +func (m *Token) GetCreated() int64 { + if m != nil { + return m.Created + } + return 0 +} + +func (m *Token) GetExpiry() int64 { + if m != nil { + return m.Expiry + } + return 0 +} + +func (m *Token) GetSubject() string { + if m != nil { + return m.Subject + } + return "" +} + +func (m *Token) GetRoles() []string { + if m != nil { + return m.Roles + } + return nil +} + +func (m *Token) GetMetadata() map[string]string { + if m != nil { + return m.Metadata + } + return nil +} + +type Account struct { + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` + Secret *Token `protobuf:"bytes,2,opt,name=secret,proto3" json:"secret,omitempty"` + Roles []string `protobuf:"bytes,3,rep,name=roles,proto3" json:"roles,omitempty"` + Metadata map[string]string `protobuf:"bytes,4,rep,name=metadata,proto3" json:"metadata,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` XXX_NoUnkeyedLiteral struct{} `json:"-"` XXX_unrecognized []byte `json:"-"` XXX_sizecache int32 `json:"-"` @@ -36,7 +121,7 @@ func (m *Account) Reset() { *m = Account{} } func (m *Account) String() string { return proto.CompactTextString(m) } func (*Account) ProtoMessage() {} func (*Account) Descriptor() ([]byte, []int) { - return fileDescriptor_de609d4872dacc78, []int{0} + return fileDescriptor_21300bfacc51fc2a, []int{1} } func (m *Account) XXX_Unmarshal(b []byte) error { @@ -64,28 +149,14 @@ func (m *Account) GetId() string { return "" } -func (m *Account) GetToken() string { +func (m *Account) GetSecret() *Token { if m != nil { - return m.Token + return m.Secret } - return "" + return nil } -func (m *Account) GetCreated() int64 { - if m != nil { - return m.Created - } - return 0 -} - -func (m *Account) GetExpiry() int64 { - if m != nil { - return m.Expiry - } - return 0 -} - -func (m *Account) GetRoles() []*Role { +func (m *Account) GetRoles() []string { if m != nil { return m.Roles } @@ -99,56 +170,10 @@ func (m *Account) GetMetadata() map[string]string { return nil } -type Role struct { - Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` - Resource *Resource `protobuf:"bytes,2,opt,name=resource,proto3" json:"resource,omitempty"` - XXX_NoUnkeyedLiteral struct{} `json:"-"` - XXX_unrecognized []byte `json:"-"` - XXX_sizecache int32 `json:"-"` -} - -func (m *Role) Reset() { *m = Role{} } -func (m *Role) String() string { return proto.CompactTextString(m) } -func (*Role) ProtoMessage() {} -func (*Role) Descriptor() ([]byte, []int) { - return fileDescriptor_de609d4872dacc78, []int{1} -} - -func (m *Role) XXX_Unmarshal(b []byte) error { - return xxx_messageInfo_Role.Unmarshal(m, b) -} -func (m *Role) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { - return xxx_messageInfo_Role.Marshal(b, m, deterministic) -} -func (m *Role) XXX_Merge(src proto.Message) { - xxx_messageInfo_Role.Merge(m, src) -} -func (m *Role) XXX_Size() int { - return xxx_messageInfo_Role.Size(m) -} -func (m *Role) XXX_DiscardUnknown() { - xxx_messageInfo_Role.DiscardUnknown(m) -} - -var xxx_messageInfo_Role proto.InternalMessageInfo - -func (m *Role) GetName() string { - if m != nil { - return m.Name - } - return "" -} - -func (m *Role) GetResource() *Resource { - if m != nil { - return m.Resource - } - return nil -} - type Resource struct { Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` Type string `protobuf:"bytes,2,opt,name=type,proto3" json:"type,omitempty"` + Endpoint string `protobuf:"bytes,3,opt,name=endpoint,proto3" json:"endpoint,omitempty"` XXX_NoUnkeyedLiteral struct{} `json:"-"` XXX_unrecognized []byte `json:"-"` XXX_sizecache int32 `json:"-"` @@ -158,7 +183,7 @@ func (m *Resource) Reset() { *m = Resource{} } func (m *Resource) String() string { return proto.CompactTextString(m) } func (*Resource) ProtoMessage() {} func (*Resource) Descriptor() ([]byte, []int) { - return fileDescriptor_de609d4872dacc78, []int{2} + return fileDescriptor_21300bfacc51fc2a, []int{2} } func (m *Resource) XXX_Unmarshal(b []byte) error { @@ -193,18 +218,28 @@ func (m *Resource) GetType() string { return "" } +func (m *Resource) GetEndpoint() string { + if m != nil { + return m.Endpoint + } + return "" +} + type GenerateRequest struct { - Account *Account `protobuf:"bytes,1,opt,name=account,proto3" json:"account,omitempty"` - XXX_NoUnkeyedLiteral struct{} `json:"-"` - XXX_unrecognized []byte `json:"-"` - XXX_sizecache int32 `json:"-"` + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` + Roles []string `protobuf:"bytes,2,rep,name=roles,proto3" json:"roles,omitempty"` + Metadata map[string]string `protobuf:"bytes,3,rep,name=metadata,proto3" json:"metadata,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` + SecretExpiry int64 `protobuf:"varint,4,opt,name=secret_expiry,json=secretExpiry,proto3" json:"secret_expiry,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` } func (m *GenerateRequest) Reset() { *m = GenerateRequest{} } func (m *GenerateRequest) String() string { return proto.CompactTextString(m) } func (*GenerateRequest) ProtoMessage() {} func (*GenerateRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_de609d4872dacc78, []int{3} + return fileDescriptor_21300bfacc51fc2a, []int{3} } func (m *GenerateRequest) XXX_Unmarshal(b []byte) error { @@ -225,13 +260,34 @@ func (m *GenerateRequest) XXX_DiscardUnknown() { var xxx_messageInfo_GenerateRequest proto.InternalMessageInfo -func (m *GenerateRequest) GetAccount() *Account { +func (m *GenerateRequest) GetId() string { if m != nil { - return m.Account + return m.Id + } + return "" +} + +func (m *GenerateRequest) GetRoles() []string { + if m != nil { + return m.Roles } return nil } +func (m *GenerateRequest) GetMetadata() map[string]string { + if m != nil { + return m.Metadata + } + return nil +} + +func (m *GenerateRequest) GetSecretExpiry() int64 { + if m != nil { + return m.SecretExpiry + } + return 0 +} + type GenerateResponse struct { Account *Account `protobuf:"bytes,1,opt,name=account,proto3" json:"account,omitempty"` XXX_NoUnkeyedLiteral struct{} `json:"-"` @@ -243,7 +299,7 @@ func (m *GenerateResponse) Reset() { *m = GenerateResponse{} } func (m *GenerateResponse) String() string { return proto.CompactTextString(m) } func (*GenerateResponse) ProtoMessage() {} func (*GenerateResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_de609d4872dacc78, []int{4} + return fileDescriptor_21300bfacc51fc2a, []int{4} } func (m *GenerateResponse) XXX_Unmarshal(b []byte) error { @@ -271,18 +327,97 @@ func (m *GenerateResponse) GetAccount() *Account { return nil } -type VerifyRequest struct { - Token string `protobuf:"bytes,1,opt,name=token,proto3" json:"token,omitempty"` +type GrantRequest struct { + Role string `protobuf:"bytes,1,opt,name=role,proto3" json:"role,omitempty"` + Resource *Resource `protobuf:"bytes,2,opt,name=resource,proto3" json:"resource,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *GrantRequest) Reset() { *m = GrantRequest{} } +func (m *GrantRequest) String() string { return proto.CompactTextString(m) } +func (*GrantRequest) ProtoMessage() {} +func (*GrantRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_21300bfacc51fc2a, []int{5} +} + +func (m *GrantRequest) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_GrantRequest.Unmarshal(m, b) +} +func (m *GrantRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_GrantRequest.Marshal(b, m, deterministic) +} +func (m *GrantRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_GrantRequest.Merge(m, src) +} +func (m *GrantRequest) XXX_Size() int { + return xxx_messageInfo_GrantRequest.Size(m) +} +func (m *GrantRequest) XXX_DiscardUnknown() { + xxx_messageInfo_GrantRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_GrantRequest proto.InternalMessageInfo + +func (m *GrantRequest) GetRole() string { + if m != nil { + return m.Role + } + return "" +} + +func (m *GrantRequest) GetResource() *Resource { + if m != nil { + return m.Resource + } + return nil +} + +type GrantResponse struct { XXX_NoUnkeyedLiteral struct{} `json:"-"` XXX_unrecognized []byte `json:"-"` XXX_sizecache int32 `json:"-"` } +func (m *GrantResponse) Reset() { *m = GrantResponse{} } +func (m *GrantResponse) String() string { return proto.CompactTextString(m) } +func (*GrantResponse) ProtoMessage() {} +func (*GrantResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_21300bfacc51fc2a, []int{6} +} + +func (m *GrantResponse) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_GrantResponse.Unmarshal(m, b) +} +func (m *GrantResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_GrantResponse.Marshal(b, m, deterministic) +} +func (m *GrantResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_GrantResponse.Merge(m, src) +} +func (m *GrantResponse) XXX_Size() int { + return xxx_messageInfo_GrantResponse.Size(m) +} +func (m *GrantResponse) XXX_DiscardUnknown() { + xxx_messageInfo_GrantResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_GrantResponse proto.InternalMessageInfo + +type VerifyRequest struct { + Account *Account `protobuf:"bytes,1,opt,name=account,proto3" json:"account,omitempty"` + Resource *Resource `protobuf:"bytes,2,opt,name=resource,proto3" json:"resource,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + func (m *VerifyRequest) Reset() { *m = VerifyRequest{} } func (m *VerifyRequest) String() string { return proto.CompactTextString(m) } func (*VerifyRequest) ProtoMessage() {} func (*VerifyRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_de609d4872dacc78, []int{5} + return fileDescriptor_21300bfacc51fc2a, []int{7} } func (m *VerifyRequest) XXX_Unmarshal(b []byte) error { @@ -303,15 +438,21 @@ func (m *VerifyRequest) XXX_DiscardUnknown() { var xxx_messageInfo_VerifyRequest proto.InternalMessageInfo -func (m *VerifyRequest) GetToken() string { +func (m *VerifyRequest) GetAccount() *Account { if m != nil { - return m.Token + return m.Account } - return "" + return nil +} + +func (m *VerifyRequest) GetResource() *Resource { + if m != nil { + return m.Resource + } + return nil } type VerifyResponse struct { - Account *Account `protobuf:"bytes,1,opt,name=account,proto3" json:"account,omitempty"` XXX_NoUnkeyedLiteral struct{} `json:"-"` XXX_unrecognized []byte `json:"-"` XXX_sizecache int32 `json:"-"` @@ -321,7 +462,7 @@ func (m *VerifyResponse) Reset() { *m = VerifyResponse{} } func (m *VerifyResponse) String() string { return proto.CompactTextString(m) } func (*VerifyResponse) ProtoMessage() {} func (*VerifyResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_de609d4872dacc78, []int{6} + return fileDescriptor_21300bfacc51fc2a, []int{8} } func (m *VerifyResponse) XXX_Unmarshal(b []byte) error { @@ -342,25 +483,19 @@ func (m *VerifyResponse) XXX_DiscardUnknown() { var xxx_messageInfo_VerifyResponse proto.InternalMessageInfo -func (m *VerifyResponse) GetAccount() *Account { - if m != nil { - return m.Account - } - return nil -} - type RevokeRequest struct { - Token string `protobuf:"bytes,1,opt,name=token,proto3" json:"token,omitempty"` - XXX_NoUnkeyedLiteral struct{} `json:"-"` - XXX_unrecognized []byte `json:"-"` - XXX_sizecache int32 `json:"-"` + Role string `protobuf:"bytes,1,opt,name=role,proto3" json:"role,omitempty"` + Resource *Resource `protobuf:"bytes,2,opt,name=resource,proto3" json:"resource,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` } func (m *RevokeRequest) Reset() { *m = RevokeRequest{} } func (m *RevokeRequest) String() string { return proto.CompactTextString(m) } func (*RevokeRequest) ProtoMessage() {} func (*RevokeRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_de609d4872dacc78, []int{7} + return fileDescriptor_21300bfacc51fc2a, []int{9} } func (m *RevokeRequest) XXX_Unmarshal(b []byte) error { @@ -381,13 +516,20 @@ func (m *RevokeRequest) XXX_DiscardUnknown() { var xxx_messageInfo_RevokeRequest proto.InternalMessageInfo -func (m *RevokeRequest) GetToken() string { +func (m *RevokeRequest) GetRole() string { if m != nil { - return m.Token + return m.Role } return "" } +func (m *RevokeRequest) GetResource() *Resource { + if m != nil { + return m.Resource + } + return nil +} + type RevokeResponse struct { XXX_NoUnkeyedLiteral struct{} `json:"-"` XXX_unrecognized []byte `json:"-"` @@ -398,7 +540,7 @@ func (m *RevokeResponse) Reset() { *m = RevokeResponse{} } func (m *RevokeResponse) String() string { return proto.CompactTextString(m) } func (*RevokeResponse) ProtoMessage() {} func (*RevokeResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_de609d4872dacc78, []int{8} + return fileDescriptor_21300bfacc51fc2a, []int{10} } func (m *RevokeResponse) XXX_Unmarshal(b []byte) error { @@ -419,50 +561,235 @@ func (m *RevokeResponse) XXX_DiscardUnknown() { var xxx_messageInfo_RevokeResponse proto.InternalMessageInfo +type InspectRequest struct { + Token string `protobuf:"bytes,1,opt,name=token,proto3" json:"token,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *InspectRequest) Reset() { *m = InspectRequest{} } +func (m *InspectRequest) String() string { return proto.CompactTextString(m) } +func (*InspectRequest) ProtoMessage() {} +func (*InspectRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_21300bfacc51fc2a, []int{11} +} + +func (m *InspectRequest) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_InspectRequest.Unmarshal(m, b) +} +func (m *InspectRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_InspectRequest.Marshal(b, m, deterministic) +} +func (m *InspectRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_InspectRequest.Merge(m, src) +} +func (m *InspectRequest) XXX_Size() int { + return xxx_messageInfo_InspectRequest.Size(m) +} +func (m *InspectRequest) XXX_DiscardUnknown() { + xxx_messageInfo_InspectRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_InspectRequest proto.InternalMessageInfo + +func (m *InspectRequest) GetToken() string { + if m != nil { + return m.Token + } + return "" +} + +type InspectResponse struct { + Account *Account `protobuf:"bytes,1,opt,name=account,proto3" json:"account,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *InspectResponse) Reset() { *m = InspectResponse{} } +func (m *InspectResponse) String() string { return proto.CompactTextString(m) } +func (*InspectResponse) ProtoMessage() {} +func (*InspectResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_21300bfacc51fc2a, []int{12} +} + +func (m *InspectResponse) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_InspectResponse.Unmarshal(m, b) +} +func (m *InspectResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_InspectResponse.Marshal(b, m, deterministic) +} +func (m *InspectResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_InspectResponse.Merge(m, src) +} +func (m *InspectResponse) XXX_Size() int { + return xxx_messageInfo_InspectResponse.Size(m) +} +func (m *InspectResponse) XXX_DiscardUnknown() { + xxx_messageInfo_InspectResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_InspectResponse proto.InternalMessageInfo + +func (m *InspectResponse) GetAccount() *Account { + if m != nil { + return m.Account + } + return nil +} + +type RefreshRequest struct { + Secret string `protobuf:"bytes,1,opt,name=secret,proto3" json:"secret,omitempty"` + TokenExpiry int64 `protobuf:"varint,2,opt,name=token_expiry,json=tokenExpiry,proto3" json:"token_expiry,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *RefreshRequest) Reset() { *m = RefreshRequest{} } +func (m *RefreshRequest) String() string { return proto.CompactTextString(m) } +func (*RefreshRequest) ProtoMessage() {} +func (*RefreshRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_21300bfacc51fc2a, []int{13} +} + +func (m *RefreshRequest) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_RefreshRequest.Unmarshal(m, b) +} +func (m *RefreshRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_RefreshRequest.Marshal(b, m, deterministic) +} +func (m *RefreshRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_RefreshRequest.Merge(m, src) +} +func (m *RefreshRequest) XXX_Size() int { + return xxx_messageInfo_RefreshRequest.Size(m) +} +func (m *RefreshRequest) XXX_DiscardUnknown() { + xxx_messageInfo_RefreshRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_RefreshRequest proto.InternalMessageInfo + +func (m *RefreshRequest) GetSecret() string { + if m != nil { + return m.Secret + } + return "" +} + +func (m *RefreshRequest) GetTokenExpiry() int64 { + if m != nil { + return m.TokenExpiry + } + return 0 +} + +type RefreshResponse struct { + Token *Token `protobuf:"bytes,1,opt,name=token,proto3" json:"token,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *RefreshResponse) Reset() { *m = RefreshResponse{} } +func (m *RefreshResponse) String() string { return proto.CompactTextString(m) } +func (*RefreshResponse) ProtoMessage() {} +func (*RefreshResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_21300bfacc51fc2a, []int{14} +} + +func (m *RefreshResponse) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_RefreshResponse.Unmarshal(m, b) +} +func (m *RefreshResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_RefreshResponse.Marshal(b, m, deterministic) +} +func (m *RefreshResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_RefreshResponse.Merge(m, src) +} +func (m *RefreshResponse) XXX_Size() int { + return xxx_messageInfo_RefreshResponse.Size(m) +} +func (m *RefreshResponse) XXX_DiscardUnknown() { + xxx_messageInfo_RefreshResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_RefreshResponse proto.InternalMessageInfo + +func (m *RefreshResponse) GetToken() *Token { + if m != nil { + return m.Token + } + return nil +} + func init() { + proto.RegisterType((*Token)(nil), "go.micro.auth.Token") + proto.RegisterMapType((map[string]string)(nil), "go.micro.auth.Token.MetadataEntry") proto.RegisterType((*Account)(nil), "go.micro.auth.Account") proto.RegisterMapType((map[string]string)(nil), "go.micro.auth.Account.MetadataEntry") - proto.RegisterType((*Role)(nil), "go.micro.auth.Role") proto.RegisterType((*Resource)(nil), "go.micro.auth.Resource") proto.RegisterType((*GenerateRequest)(nil), "go.micro.auth.GenerateRequest") + proto.RegisterMapType((map[string]string)(nil), "go.micro.auth.GenerateRequest.MetadataEntry") proto.RegisterType((*GenerateResponse)(nil), "go.micro.auth.GenerateResponse") + proto.RegisterType((*GrantRequest)(nil), "go.micro.auth.GrantRequest") + proto.RegisterType((*GrantResponse)(nil), "go.micro.auth.GrantResponse") proto.RegisterType((*VerifyRequest)(nil), "go.micro.auth.VerifyRequest") proto.RegisterType((*VerifyResponse)(nil), "go.micro.auth.VerifyResponse") proto.RegisterType((*RevokeRequest)(nil), "go.micro.auth.RevokeRequest") proto.RegisterType((*RevokeResponse)(nil), "go.micro.auth.RevokeResponse") + proto.RegisterType((*InspectRequest)(nil), "go.micro.auth.InspectRequest") + proto.RegisterType((*InspectResponse)(nil), "go.micro.auth.InspectResponse") + proto.RegisterType((*RefreshRequest)(nil), "go.micro.auth.RefreshRequest") + proto.RegisterType((*RefreshResponse)(nil), "go.micro.auth.RefreshResponse") } -func init() { - proto.RegisterFile("micro/go-micro/auth/service/proto/auth.proto", fileDescriptor_de609d4872dacc78) -} +func init() { proto.RegisterFile("auth/service/proto/auth.proto", fileDescriptor_21300bfacc51fc2a) } -var fileDescriptor_de609d4872dacc78 = []byte{ - // 432 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xa4, 0x53, 0x4b, 0x6f, 0xd3, 0x40, - 0x10, 0xae, 0x1d, 0xe7, 0xc1, 0x44, 0x09, 0xd1, 0x80, 0x8a, 0x15, 0xf1, 0x88, 0x56, 0x20, 0x05, - 0x09, 0x1c, 0xe4, 0x5e, 0x10, 0x5c, 0x28, 0x0f, 0xf5, 0x54, 0x21, 0xed, 0x81, 0xfb, 0xe2, 0x0c, - 0xad, 0x95, 0xc4, 0x6b, 0xd6, 0xeb, 0x08, 0xff, 0x06, 0x7e, 0x28, 0x7f, 0x03, 0x79, 0xd7, 0x1b, - 0x6a, 0xb7, 0xe5, 0x00, 0xb7, 0x79, 0x7c, 0xf3, 0xcd, 0xf7, 0x8d, 0x76, 0xe1, 0xc5, 0x2e, 0x4d, - 0x94, 0x5c, 0x5d, 0xc8, 0x97, 0x36, 0x10, 0xa5, 0xbe, 0x5c, 0x15, 0xa4, 0xf6, 0x69, 0x42, 0xab, - 0x5c, 0x49, 0x6d, 0x4b, 0x91, 0x09, 0x71, 0x72, 0x21, 0x23, 0x83, 0x8b, 0xea, 0x22, 0xfb, 0xe9, - 0xc3, 0xf0, 0x34, 0x49, 0x64, 0x99, 0x69, 0x9c, 0x82, 0x9f, 0xae, 0x43, 0x6f, 0xe1, 0x2d, 0xef, - 0x70, 0x3f, 0x5d, 0xe3, 0x7d, 0xe8, 0x6b, 0xb9, 0xa1, 0x2c, 0xf4, 0x4d, 0xc9, 0x26, 0x18, 0xc2, - 0x30, 0x51, 0x24, 0x34, 0xad, 0xc3, 0xde, 0xc2, 0x5b, 0xf6, 0xb8, 0x4b, 0xf1, 0x18, 0x06, 0xf4, - 0x23, 0x4f, 0x55, 0x15, 0x06, 0xa6, 0xd1, 0x64, 0xf8, 0x1c, 0xfa, 0x4a, 0x6e, 0xa9, 0x08, 0xfb, - 0x8b, 0xde, 0x72, 0x1c, 0xdf, 0x8b, 0x5a, 0x12, 0x22, 0x2e, 0xb7, 0xc4, 0x2d, 0x02, 0xdf, 0xc1, - 0x68, 0x47, 0x5a, 0xac, 0x85, 0x16, 0xe1, 0xc0, 0xa0, 0x9f, 0x76, 0xd0, 0x8d, 0xd8, 0xe8, 0xbc, - 0x81, 0x7d, 0xca, 0xb4, 0xaa, 0xf8, 0x61, 0x6a, 0xfe, 0x16, 0x26, 0xad, 0x16, 0xce, 0xa0, 0xb7, - 0xa1, 0xaa, 0xb1, 0x55, 0x87, 0xb5, 0xaf, 0xbd, 0xd8, 0x96, 0xe4, 0x7c, 0x99, 0xe4, 0x8d, 0xff, - 0xda, 0x63, 0x9f, 0x21, 0xa8, 0xd5, 0x20, 0x42, 0x90, 0x89, 0x1d, 0x35, 0x43, 0x26, 0xc6, 0x13, - 0x18, 0x29, 0x2a, 0x64, 0xa9, 0x12, 0x3b, 0x38, 0x8e, 0x1f, 0x74, 0x8d, 0x34, 0x6d, 0x7e, 0x00, - 0xb2, 0x18, 0x46, 0xae, 0x7a, 0x23, 0x29, 0x42, 0xa0, 0xab, 0xdc, 0x29, 0x31, 0x31, 0xfb, 0x00, - 0x77, 0xcf, 0x28, 0x23, 0x25, 0x34, 0x71, 0xfa, 0x5e, 0x52, 0xa1, 0xf1, 0x15, 0x0c, 0x85, 0xf5, - 0x6d, 0xa6, 0xc7, 0xf1, 0xf1, 0xcd, 0x57, 0xe1, 0x0e, 0xc6, 0x3e, 0xc2, 0xec, 0x0f, 0x49, 0x91, - 0xcb, 0xac, 0xa0, 0x7f, 0x60, 0x79, 0x06, 0x93, 0x2f, 0xa4, 0xd2, 0x6f, 0x95, 0x13, 0x72, 0x78, - 0x12, 0xde, 0x95, 0x27, 0xc1, 0xde, 0xc3, 0xd4, 0xc1, 0xfe, 0x67, 0x15, 0xa7, 0xbd, 0xdc, 0xd0, - 0xdf, 0x57, 0xcd, 0x60, 0xea, 0x60, 0x76, 0x55, 0xfc, 0xcb, 0x83, 0xe0, 0xb4, 0xd4, 0x97, 0x78, - 0x0e, 0x23, 0x67, 0x19, 0x1f, 0x77, 0xd6, 0x75, 0x0e, 0x3a, 0x7f, 0x72, 0x6b, 0xdf, 0xb2, 0xb2, - 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, +var fileDescriptor_21300bfacc51fc2a = []byte{ + // 663 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xb4, 0x55, 0xdd, 0x6e, 0xd3, 0x4c, + 0x10, 0xad, 0xed, 0xfc, 0x75, 0x52, 0x27, 0xd1, 0xaa, 0xea, 0x67, 0xf9, 0xa3, 0x25, 0x18, 0x84, + 0x2a, 0x54, 0xb9, 0x28, 0xbd, 0x41, 0x20, 0x10, 0x15, 0xad, 0xca, 0x8f, 0xca, 0x85, 0x85, 0x80, + 0x3b, 0xe4, 0x3a, 0x53, 0x6a, 0xd2, 0xd8, 0x66, 0xbd, 0x8e, 0xc8, 0x5b, 0xf0, 0x52, 0xbc, 0x05, + 0x77, 0xbc, 0x08, 0xda, 0xf5, 0xae, 0x6b, 0x3b, 0x09, 0x12, 0x05, 0xee, 0x76, 0x76, 0x66, 0xcf, + 0x99, 0x39, 0x73, 0xe2, 0xc0, 0xb6, 0x9f, 0xb1, 0x8b, 0xfd, 0x14, 0xe9, 0x2c, 0x0c, 0x70, 0x3f, + 0xa1, 0x31, 0x8b, 0xf7, 0xf9, 0x95, 0x2b, 0x8e, 0xc4, 0xfc, 0x18, 0xbb, 0xd3, 0x30, 0xa0, 0xb1, + 0xcb, 0x2f, 0x9d, 0xaf, 0x3a, 0x34, 0xdf, 0xc4, 0x13, 0x8c, 0xc8, 0x26, 0x34, 0x19, 0x3f, 0x58, + 0xda, 0x50, 0xdb, 0x5d, 0xf7, 0xf2, 0x80, 0x10, 0x68, 0xb0, 0x79, 0x82, 0x96, 0x2e, 0x2e, 0xc5, + 0x99, 0x58, 0xd0, 0x0e, 0x28, 0xfa, 0x0c, 0xc7, 0x96, 0x31, 0xd4, 0x76, 0x0d, 0x4f, 0x85, 0x64, + 0x0b, 0x5a, 0xf8, 0x25, 0x09, 0xe9, 0xdc, 0x6a, 0x88, 0x84, 0x8c, 0xf8, 0x8b, 0x34, 0x3b, 0xfb, + 0x84, 0x01, 0xb3, 0x9a, 0x02, 0x48, 0x85, 0x9c, 0x95, 0xc6, 0x97, 0x98, 0x5a, 0xad, 0xa1, 0xc1, + 0x59, 0x45, 0x40, 0x9e, 0x40, 0x67, 0x8a, 0xcc, 0x1f, 0xfb, 0xcc, 0xb7, 0xda, 0x43, 0x63, 0xb7, + 0x3b, 0x72, 0xdc, 0x4a, 0xdf, 0xae, 0xe8, 0xd9, 0x3d, 0x95, 0x45, 0xc7, 0x11, 0xa3, 0x73, 0xaf, + 0x78, 0x63, 0x3f, 0x02, 0xb3, 0x92, 0x22, 0x03, 0x30, 0x26, 0x38, 0x97, 0xa3, 0xf1, 0x23, 0x27, + 0x9e, 0xf9, 0x97, 0x99, 0x9a, 0x2c, 0x0f, 0x1e, 0xea, 0x0f, 0x34, 0xe7, 0xbb, 0x06, 0xed, 0xc3, + 0x20, 0x88, 0xb3, 0x88, 0x91, 0x1e, 0xe8, 0xe1, 0x58, 0x3e, 0xd3, 0xc3, 0x31, 0xd9, 0x83, 0x56, + 0x8a, 0x01, 0x45, 0x26, 0x9e, 0x75, 0x47, 0x9b, 0xcb, 0xda, 0xf2, 0x64, 0xcd, 0xd5, 0x70, 0x46, + 0x79, 0xb8, 0xa7, 0xa5, 0xe1, 0x1a, 0x62, 0xb8, 0x3b, 0x35, 0x14, 0xc9, 0xfe, 0x6f, 0xc6, 0x7b, + 0x0d, 0x1d, 0x0f, 0xd3, 0x38, 0xa3, 0x01, 0xf2, 0xed, 0x46, 0xfe, 0x14, 0xe5, 0x43, 0x71, 0x5e, + 0xba, 0x71, 0x1b, 0x3a, 0x18, 0x8d, 0x93, 0x38, 0x8c, 0x98, 0x58, 0xf9, 0xba, 0x57, 0xc4, 0xce, + 0x0f, 0x0d, 0xfa, 0x27, 0x18, 0x21, 0xf5, 0x19, 0x7a, 0xf8, 0x39, 0xc3, 0x74, 0x51, 0xb6, 0x42, + 0x08, 0xbd, 0x2c, 0xc4, 0xf3, 0x92, 0x10, 0x86, 0x10, 0x62, 0xaf, 0x26, 0x44, 0x0d, 0x77, 0x95, + 0x20, 0xe4, 0x36, 0x98, 0xb9, 0xe4, 0x1f, 0x2a, 0xf6, 0xdb, 0xc8, 0x2f, 0x8f, 0xc5, 0xdd, 0x9f, + 0xa9, 0x76, 0x04, 0x83, 0xab, 0x66, 0xd2, 0x24, 0x8e, 0x52, 0x24, 0xf7, 0xa1, 0xed, 0xe7, 0x9b, + 0x12, 0x18, 0xdd, 0xd1, 0xd6, 0xf2, 0x3d, 0x7a, 0xaa, 0xcc, 0x79, 0x07, 0x1b, 0x27, 0xd4, 0x8f, + 0x98, 0xd2, 0x89, 0x40, 0x83, 0x4b, 0xa1, 0xf4, 0xe7, 0x67, 0x72, 0x00, 0x1d, 0x2a, 0xf7, 0x23, + 0x4d, 0xf6, 0x5f, 0x0d, 0x56, 0xad, 0xcf, 0x2b, 0x0a, 0x9d, 0x3e, 0x98, 0x12, 0x38, 0xef, 0xcd, + 0x99, 0x81, 0xf9, 0x16, 0x69, 0x78, 0x3e, 0x57, 0x54, 0xbf, 0xdd, 0xec, 0xf5, 0x1a, 0x19, 0x40, + 0x4f, 0xf1, 0xca, 0x4e, 0xde, 0x83, 0xe9, 0xe1, 0x2c, 0x9e, 0xe0, 0x5f, 0x1f, 0x7a, 0x00, 0x3d, + 0x85, 0x2c, 0xb9, 0xee, 0x42, 0xef, 0x45, 0x94, 0x26, 0x18, 0x14, 0x0a, 0x2f, 0xfd, 0xaa, 0x39, + 0xcf, 0xa0, 0x5f, 0xd4, 0x5d, 0x7b, 0x99, 0xaf, 0x38, 0xfd, 0x39, 0xc5, 0xf4, 0x42, 0x91, 0x6d, + 0x15, 0x5f, 0x87, 0x9c, 0x4d, 0x7d, 0x07, 0x6e, 0xc1, 0x86, 0xe0, 0x55, 0xee, 0xd4, 0x85, 0x3b, + 0xbb, 0xe2, 0x2e, 0x37, 0xa7, 0xf3, 0x18, 0xfa, 0x05, 0x98, 0xec, 0xe8, 0x5e, 0xb9, 0xf5, 0x55, + 0x9f, 0x9a, 0xbc, 0x64, 0xf4, 0xcd, 0x80, 0xc6, 0x61, 0xc6, 0x2e, 0xc8, 0x29, 0x74, 0x94, 0x4f, + 0xc9, 0xce, 0xaf, 0x7f, 0x4d, 0xf6, 0xcd, 0x95, 0x79, 0x29, 0xe7, 0x1a, 0x39, 0x82, 0xa6, 0xf0, + 0x15, 0xf9, 0xbf, 0x5e, 0x5b, 0xb2, 0xb1, 0x7d, 0x63, 0x79, 0xb2, 0x40, 0x39, 0x81, 0x56, 0x6e, + 0x0a, 0x52, 0xaf, 0xac, 0x78, 0xd4, 0xde, 0x5e, 0x91, 0x2d, 0x03, 0xe5, 0x1b, 0x5f, 0x00, 0xaa, + 0x58, 0x6c, 0x01, 0xa8, 0x66, 0x93, 0x35, 0xf2, 0x12, 0xda, 0xd2, 0x00, 0xa4, 0x5e, 0x5b, 0x35, + 0x90, 0xbd, 0xb3, 0x2a, 0x5d, 0xc6, 0x92, 0xab, 0x23, 0x8b, 0xbc, 0x65, 0x7f, 0x2c, 0x60, 0xd5, + 0x36, 0xee, 0xac, 0x9d, 0xb5, 0xc4, 0x9f, 0xf4, 0xc1, 0xcf, 0x00, 0x00, 0x00, 0xff, 0xff, 0xb7, + 0xf8, 0x55, 0xb6, 0xc5, 0x07, 0x00, 0x00, } diff --git a/auth/service/proto/auth.pb.micro.go b/auth/service/proto/auth.pb.micro.go index 72b197fa..dccf46b8 100644 --- a/auth/service/proto/auth.pb.micro.go +++ b/auth/service/proto/auth.pb.micro.go @@ -1,5 +1,5 @@ // Code generated by protoc-gen-micro. DO NOT EDIT. -// source: micro/go-micro/auth/service/proto/auth.proto +// source: auth/service/proto/auth.proto package go_micro_auth @@ -35,8 +35,11 @@ var _ server.Option type AuthService interface { Generate(ctx context.Context, in *GenerateRequest, opts ...client.CallOption) (*GenerateResponse, error) + Grant(ctx context.Context, in *GrantRequest, opts ...client.CallOption) (*GrantResponse, error) Verify(ctx context.Context, in *VerifyRequest, opts ...client.CallOption) (*VerifyResponse, error) Revoke(ctx context.Context, in *RevokeRequest, opts ...client.CallOption) (*RevokeResponse, error) + Inspect(ctx context.Context, in *InspectRequest, opts ...client.CallOption) (*InspectResponse, error) + Refresh(ctx context.Context, in *RefreshRequest, opts ...client.CallOption) (*RefreshResponse, error) } type authService struct { @@ -61,6 +64,16 @@ func (c *authService) Generate(ctx context.Context, in *GenerateRequest, opts .. return out, nil } +func (c *authService) Grant(ctx context.Context, in *GrantRequest, opts ...client.CallOption) (*GrantResponse, error) { + req := c.c.NewRequest(c.name, "Auth.Grant", in) + out := new(GrantResponse) + err := c.c.Call(ctx, req, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + func (c *authService) Verify(ctx context.Context, in *VerifyRequest, opts ...client.CallOption) (*VerifyResponse, error) { req := c.c.NewRequest(c.name, "Auth.Verify", in) out := new(VerifyResponse) @@ -81,19 +94,45 @@ func (c *authService) Revoke(ctx context.Context, in *RevokeRequest, opts ...cli return out, nil } +func (c *authService) Inspect(ctx context.Context, in *InspectRequest, opts ...client.CallOption) (*InspectResponse, error) { + req := c.c.NewRequest(c.name, "Auth.Inspect", in) + out := new(InspectResponse) + err := c.c.Call(ctx, req, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *authService) Refresh(ctx context.Context, in *RefreshRequest, opts ...client.CallOption) (*RefreshResponse, error) { + req := c.c.NewRequest(c.name, "Auth.Refresh", in) + out := new(RefreshResponse) + err := c.c.Call(ctx, req, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + // Server API for Auth service type AuthHandler interface { Generate(context.Context, *GenerateRequest, *GenerateResponse) error + Grant(context.Context, *GrantRequest, *GrantResponse) error Verify(context.Context, *VerifyRequest, *VerifyResponse) error Revoke(context.Context, *RevokeRequest, *RevokeResponse) error + Inspect(context.Context, *InspectRequest, *InspectResponse) error + Refresh(context.Context, *RefreshRequest, *RefreshResponse) error } func RegisterAuthHandler(s server.Server, hdlr AuthHandler, opts ...server.HandlerOption) error { type auth interface { Generate(ctx context.Context, in *GenerateRequest, out *GenerateResponse) error + Grant(ctx context.Context, in *GrantRequest, out *GrantResponse) error Verify(ctx context.Context, in *VerifyRequest, out *VerifyResponse) error Revoke(ctx context.Context, in *RevokeRequest, out *RevokeResponse) error + Inspect(ctx context.Context, in *InspectRequest, out *InspectResponse) error + Refresh(ctx context.Context, in *RefreshRequest, out *RefreshResponse) error } type Auth struct { auth @@ -110,6 +149,10 @@ func (h *authHandler) Generate(ctx context.Context, in *GenerateRequest, out *Ge return h.AuthHandler.Generate(ctx, in, out) } +func (h *authHandler) Grant(ctx context.Context, in *GrantRequest, out *GrantResponse) error { + return h.AuthHandler.Grant(ctx, in, out) +} + func (h *authHandler) Verify(ctx context.Context, in *VerifyRequest, out *VerifyResponse) error { return h.AuthHandler.Verify(ctx, in, out) } @@ -117,3 +160,11 @@ func (h *authHandler) Verify(ctx context.Context, in *VerifyRequest, out *Verify func (h *authHandler) Revoke(ctx context.Context, in *RevokeRequest, out *RevokeResponse) error { return h.AuthHandler.Revoke(ctx, in, out) } + +func (h *authHandler) Inspect(ctx context.Context, in *InspectRequest, out *InspectResponse) error { + return h.AuthHandler.Inspect(ctx, in, out) +} + +func (h *authHandler) Refresh(ctx context.Context, in *RefreshRequest, out *RefreshResponse) error { + return h.AuthHandler.Refresh(ctx, in, out) +} diff --git a/auth/service/proto/auth.proto b/auth/service/proto/auth.proto index 7f60f8bb..6b2587bb 100644 --- a/auth/service/proto/auth.proto +++ b/auth/service/proto/auth.proto @@ -3,48 +3,82 @@ syntax = "proto3"; package go.micro.auth; service Auth { - rpc Generate(GenerateRequest) returns (GenerateResponse) {}; - rpc Verify(VerifyRequest) returns (VerifyResponse) {}; - rpc Revoke(RevokeRequest) returns (RevokeResponse) {}; + rpc Generate(GenerateRequest) returns (GenerateResponse) {}; + rpc Grant(GrantRequest) returns (GrantResponse) {}; + rpc Verify(VerifyRequest) returns (VerifyResponse) {}; + rpc Revoke(RevokeRequest) returns (RevokeResponse) {}; + rpc Inspect(InspectRequest) returns (InspectResponse) {}; + rpc Refresh(RefreshRequest) returns (RefreshResponse) {}; } -message Account{ - string id = 1; - string token = 2; +message Token { + string token = 1; + string type = 2; int64 created = 3; int64 expiry = 4; - repeated Role roles = 5; - map metadata = 6; + string subject = 5; + repeated string roles = 6; + map metadata = 7; } -message Role { - string name = 1; - Resource resource = 2; +message Account { + string id = 1; + Token secret = 2; + repeated string roles = 3; + map metadata = 4; } message Resource{ string name = 1; string type = 2; + string endpoint = 3; } message GenerateRequest { - Account account = 1; + string id = 1; + repeated string roles = 2; + map metadata = 3; + int64 secret_expiry = 4; } message GenerateResponse { Account account = 1; } -message VerifyRequest { - string token = 1; +message GrantRequest { + string role = 1; + Resource resource = 2; } -message VerifyResponse { +message GrantResponse {} + +message VerifyRequest { Account account = 1; + Resource resource = 2; } +message VerifyResponse {} + message RevokeRequest { - string token = 1; + string role = 1; + Resource resource = 2; } message RevokeResponse {} + +message InspectRequest { + string token = 1; +} + +message InspectResponse { + Account account = 1; +} + +message RefreshRequest { + string secret = 1; + int64 token_expiry = 2; +} + +message RefreshResponse { + Token token = 1; +} \ No newline at end of file diff --git a/auth/service/service.go b/auth/service/service.go index 6131eda2..3f11848b 100644 --- a/auth/service/service.go +++ b/auth/service/service.go @@ -2,10 +2,13 @@ package service import ( "context" + "strings" "time" "github.com/micro/go-micro/v2/auth" pb "github.com/micro/go-micro/v2/auth/service/proto" + "github.com/micro/go-micro/v2/auth/token" + "github.com/micro/go-micro/v2/auth/token/jwt" "github.com/micro/go-micro/v2/client" ) @@ -20,13 +23,14 @@ func NewAuth(opts ...auth.Option) auth.Auth { type svc struct { options auth.Options auth pb.AuthService + jwt token.Provider } func (s *svc) String() string { return "service" } -func (s *svc) Init(opts ...auth.Option) error { +func (s *svc) Init(opts ...auth.Option) { for _, o := range opts { o(&s.options) } @@ -34,99 +38,140 @@ func (s *svc) Init(opts ...auth.Option) error { dc := client.DefaultClient s.auth = pb.NewAuthService("go.micro.auth", dc) - return nil + // if we have a JWT public key passed as an option, + // we can decode tokens with the type "JWT" locally + // and not have to make an RPC call + if key := s.options.PublicKey; len(key) > 0 { + s.jwt = jwt.NewTokenProvider(token.WithPublicKey(key)) + } } func (s *svc) Options() auth.Options { return s.options } -// Generate a new auth account +// Generate a new account func (s *svc) Generate(id string, opts ...auth.GenerateOption) (*auth.Account, error) { - // construct the request options := auth.NewGenerateOptions(opts...) - sa := &auth.Account{ - Id: id, - Roles: options.Roles, - Metadata: options.Metadata, - } - req := &pb.GenerateRequest{Account: serializeAccount(sa)} - // execute the request - resp, err := s.auth.Generate(context.Background(), req) + rsp, err := s.auth.Generate(context.TODO(), &pb.GenerateRequest{ + Id: id, + Roles: options.Roles, + Metadata: options.Metadata, + SecretExpiry: int64(options.SecretExpiry.Seconds()), + }) if err != nil { return nil, err } - // format the response - return deserializeAccount(resp.Account), nil + return serializeAccount(rsp.Account), nil } -// Revoke an authorization account -func (s *svc) Revoke(token string) error { - // contruct the request - req := &pb.RevokeRequest{Token: token} - - // execute the request - _, err := s.auth.Revoke(context.Background(), req) +// Grant access to a resource +func (s *svc) Grant(role string, res *auth.Resource) error { + _, err := s.auth.Grant(context.TODO(), &pb.GrantRequest{ + Role: role, + Resource: &pb.Resource{ + Type: res.Type, + Name: res.Name, + Endpoint: res.Endpoint, + }, + }) return err } -// Verify an account token -func (s *svc) Verify(token string) (*auth.Account, error) { - resp, err := s.auth.Verify(context.Background(), &pb.VerifyRequest{Token: token}) +// Revoke access to a resource +func (s *svc) Revoke(role string, res *auth.Resource) error { + _, err := s.auth.Revoke(context.TODO(), &pb.RevokeRequest{ + Role: role, + Resource: &pb.Resource{ + Type: res.Type, + Name: res.Name, + Endpoint: res.Endpoint, + }, + }) + return err +} + +// Verify an account has access to a resource +func (s *svc) Verify(acc *auth.Account, res *auth.Resource) error { + _, err := s.auth.Verify(context.TODO(), &pb.VerifyRequest{ + Account: &pb.Account{ + Id: acc.ID, + Roles: acc.Roles, + }, + Resource: &pb.Resource{ + Type: res.Type, + Name: res.Name, + Endpoint: res.Endpoint, + }, + }) + return err +} + +// Inspect a token +func (s *svc) Inspect(token string) (*auth.Account, error) { + // try to decode JWT locally and fall back to srv if an error + // occurs, TODO: find a better way of determining if the token + // is a JWT, possibly update the interface to take an auth.Token + // and not just the string + if len(strings.Split(token, ".")) == 3 && s.jwt != nil { + if tok, err := s.jwt.Inspect(token); err == nil { + return &auth.Account{ + ID: tok.Subject, + Roles: tok.Roles, + Metadata: tok.Metadata, + }, nil + } + } + + rsp, err := s.auth.Inspect(context.TODO(), &pb.InspectRequest{ + Token: token, + }) if err != nil { return nil, err } - return deserializeAccount(resp.Account), nil + return serializeAccount(rsp.Account), nil } -func serializeAccount(sa *auth.Account) *pb.Account { - roles := make([]*pb.Role, len(sa.Roles)) - for i, r := range sa.Roles { - roles[i] = &pb.Role{ - Name: r.Name, - } +// Refresh an account using a secret +func (s *svc) Refresh(secret string, opts ...auth.RefreshOption) (*auth.Token, error) { + options := auth.NewRefreshOptions(opts...) - if r.Resource != nil { - roles[i].Resource = &pb.Resource{ - Name: r.Resource.Name, - Type: r.Resource.Type, - } - } + rsp, err := s.auth.Refresh(context.Background(), &pb.RefreshRequest{ + Secret: secret, + TokenExpiry: int64(options.TokenExpiry.Seconds()), + }) + if err != nil { + return nil, err } - return &pb.Account{ - Id: sa.Id, - Roles: roles, - Metadata: sa.Metadata, + return serializeToken(rsp.Token), nil +} + +func serializeToken(t *pb.Token) *auth.Token { + return &auth.Token{ + Token: t.Token, + Type: t.Type, + Created: time.Unix(t.Created, 0), + Expiry: time.Unix(t.Expiry, 0), + Subject: t.Subject, + Roles: t.Roles, + Metadata: t.Metadata, } } -func deserializeAccount(a *pb.Account) *auth.Account { - // format the response - sa := &auth.Account{ - Id: a.Id, - Token: a.Token, - Created: time.Unix(a.Created, 0), - Expiry: time.Unix(a.Expiry, 0), +func serializeAccount(a *pb.Account) *auth.Account { + var secret *auth.Token + if a.Secret != nil { + secret = serializeToken(a.Secret) + } + + return &auth.Account{ + ID: a.Id, + Roles: a.Roles, Metadata: a.Metadata, + Secret: secret, } - - sa.Roles = make([]*auth.Role, len(a.Roles)) - for i, r := range a.Roles { - sa.Roles[i] = &auth.Role{ - Name: r.Name, - } - - if r.Resource != nil { - sa.Roles[i].Resource = &auth.Resource{ - Name: r.Resource.Name, - Type: r.Resource.Type, - } - } - } - - return sa } diff --git a/auth/store/rules.go b/auth/store/rules.go new file mode 100644 index 00000000..29270eeb --- /dev/null +++ b/auth/store/rules.go @@ -0,0 +1,72 @@ +package store + +import ( + "encoding/json" + "strings" + + "github.com/micro/go-micro/v2/auth" + "github.com/micro/go-micro/v2/store" +) + +// Rule is an access control rule +type Rule struct { + Role string `json:"rule"` + Resource *auth.Resource `json:"resource"` +} + +// Key to be used when written to the store +func (r *Rule) Key() string { + comps := []string{r.Resource.Type, r.Resource.Name, r.Resource.Endpoint, r.Role} + return strings.Join(comps, "/") +} + +// Bytes returns json encoded bytes +func (r *Rule) Bytes() []byte { + bytes, _ := json.Marshal(r) + return bytes +} + +// isValidRule returns a bool, indicating if a rule permits access to a +// resource for a given account +func isValidRule(rule Rule, acc *auth.Account, res *auth.Resource) bool { + if rule.Role == "*" { + return true + } + + for _, role := range acc.Roles { + if rule.Role == role { + return true + } + + // allow user.anything if role is user.* + if strings.HasSuffix(rule.Role, ".*") && strings.HasPrefix(rule.Role, role+".") { + return true + } + } + + return false +} + +// listRules gets all the rules from the store which have a key +// prefix matching the filters +func (s *Store) listRules(filters ...string) ([]Rule, error) { + // get the records from the store + prefix := strings.Join(filters, "/") + recs, err := s.opts.Store.Read(prefix, store.ReadPrefix()) + if err != nil { + return nil, err + } + + // unmarshal the records + rules := make([]Rule, 0, len(recs)) + for _, rec := range recs { + var r Rule + if err := json.Unmarshal(rec.Value, &r); err != nil { + return nil, err + } + rules = append(rules, r) + } + + // return the rules + return rules, nil +} diff --git a/auth/store/store.go b/auth/store/store.go index 3f3f4d42..de5d5e75 100644 --- a/auth/store/store.go +++ b/auth/store/store.go @@ -1,130 +1,159 @@ package store import ( - "bytes" - "encoding/gob" - "time" - - "github.com/google/uuid" "github.com/micro/go-micro/v2/auth" - "github.com/micro/go-micro/v2/errors" + "github.com/micro/go-micro/v2/auth/token" + "github.com/micro/go-micro/v2/auth/token/basic" "github.com/micro/go-micro/v2/store" + memStore "github.com/micro/go-micro/v2/store/memory" ) -type Auth struct { - store store.Store - opts auth.Options -} - -// NewAuth returns an instance of store auth +// NewAuth returns a new default registry which is store func NewAuth(opts ...auth.Option) auth.Auth { - var options auth.Options + var s Store + s.Init(opts...) + return &s +} +// Store implementation of auth +type Store struct { + secretProvider token.Provider + tokenProvider token.Provider + opts auth.Options +} + +// String returns store +func (s *Store) String() string { + return "store" +} + +// Init the auth +func (s *Store) Init(opts ...auth.Option) { for _, o := range opts { - o(&options) + o(&s.opts) } - return &Auth{ - store: store.DefaultStore, - opts: options, + // use the default store as a fallback + if s.opts.Store == nil { + s.opts.Store = store.DefaultStore + } + + // noop will not work for auth + if s.opts.Store.String() == "noop" { + s.opts.Store = memStore.NewStore() + } + + if s.tokenProvider == nil { + s.tokenProvider = basic.NewTokenProvider(token.WithStore(s.opts.Store)) + } + if s.secretProvider == nil { + s.secretProvider = basic.NewTokenProvider(token.WithStore(s.opts.Store)) } } -// Init the auth package -func (a *Auth) Init(opts ...auth.Option) error { - for _, o := range opts { - o(&a.opts) - } - return nil +// Options returns the options +func (s *Store) Options() auth.Options { + return s.opts } -// Options returns the options set -func (a *Auth) Options() auth.Options { - return a.opts -} - -// Generate a new auth Account -func (a *Auth) Generate(id string, opts ...auth.GenerateOption) (*auth.Account, error) { - // generate the token - token, err := uuid.NewUUID() - if err != nil { - return nil, err - } - +// Generate a new account +func (s *Store) Generate(id string, opts ...auth.GenerateOption) (*auth.Account, error) { // parse the options options := auth.NewGenerateOptions(opts...) - // construct the account - sa := auth.Account{ - Id: id, - Token: token.String(), - Created: time.Now(), - Metadata: options.Metadata, - Roles: options.Roles, + // Generate a long-lived secret + secretOpts := []token.GenerateOption{ + token.WithExpiry(options.SecretExpiry), + token.WithMetadata(options.Metadata), + token.WithRoles(options.Roles), } - - // encode the data to bytes - // TODO: replace with json - buf := &bytes.Buffer{} - e := gob.NewEncoder(buf) - if err := e.Encode(sa); err != nil { - return nil, err - } - - // write to the store - err = a.store.Write(&store.Record{ - Key: token.String(), - Value: buf.Bytes(), - }) + secret, err := s.secretProvider.Generate(id, secretOpts...) if err != nil { return nil, err } - // return the result - return &sa, nil + // return the account + return &auth.Account{ + ID: id, + Roles: options.Roles, + Metadata: options.Metadata, + Secret: secret, + }, nil } -// Revoke an authorization Account -func (a *Auth) Revoke(token string) error { - records, err := a.store.Read(token, store.ReadSuffix()) - if err != nil { - return err - } - if len(records) == 0 { - return errors.BadRequest("go.micro.auth", "token not found") +// Grant access to a resource +func (s *Store) Grant(role string, res *auth.Resource) error { + r := Rule{role, res} + return s.opts.Store.Write(&store.Record{Key: r.Key(), Value: r.Bytes()}) +} + +// Revoke access to a resource +func (s *Store) Revoke(role string, res *auth.Resource) error { + r := Rule{role, res} + + err := s.opts.Store.Delete(r.Key()) + if err == store.ErrNotFound { + return auth.ErrNotFound } - for _, r := range records { - if err := a.store.Delete(r.Key); err != nil { - return errors.InternalServerError("go.micro.auth", "error deleting from store") + return err +} + +// Verify an account has access to a resource +func (s *Store) Verify(acc *auth.Account, res *auth.Resource) error { + queries := [][]string{ + {res.Type, "*"}, // check for wildcard resource type, e.g. service.* + {res.Type, res.Name, "*"}, // check for wildcard name, e.g. service.foo* + {res.Type, res.Name, res.Endpoint, "*"}, // check for wildcard endpoints, e.g. service.foo.ListFoo:* + {res.Type, res.Name, res.Endpoint}, // check for specific role, e.g. service.foo.ListFoo:admin + } + + for _, q := range queries { + rules, err := s.listRules(q...) + if err != nil { + return err + } + + for _, rule := range rules { + if isValidRule(rule, acc, res) { + return nil + } } } - return nil + return auth.ErrForbidden } -// Verify an account token -func (a *Auth) Verify(token string) (*auth.Account, error) { - // lookup the record by token - records, err := a.store.Read(token, store.ReadSuffix()) - if err == store.ErrNotFound || len(records) == 0 { - return nil, errors.Unauthorized("go.micro.auth", "invalid token") +// Inspect a token +func (s *Store) Inspect(t string) (*auth.Account, error) { + tok, err := s.tokenProvider.Inspect(t) + if err == token.ErrInvalidToken || err == token.ErrNotFound { + return nil, auth.ErrInvalidToken } else if err != nil { - return nil, errors.InternalServerError("go.micro.auth", "error reading store") + return nil, err } - // decode the result - // TODO: replace with json - b := bytes.NewBuffer(records[0].Value) - decoder := gob.NewDecoder(b) - var sa auth.Account - err = decoder.Decode(&sa) - - // return the result - return &sa, err + return &auth.Account{ + ID: tok.Subject, + Roles: tok.Roles, + Metadata: tok.Metadata, + }, nil } -// String returns the implementation -func (a *Auth) String() string { - return "store" +// Refresh an account using a secret +func (s *Store) Refresh(secret string, opts ...auth.RefreshOption) (*auth.Token, error) { + sec, err := s.secretProvider.Inspect(secret) + if err == token.ErrInvalidToken || err == token.ErrNotFound { + return nil, auth.ErrInvalidToken + } else if err != nil { + return nil, err + } + + options := auth.NewRefreshOptions(opts...) + + return s.tokenProvider.Generate(sec.Subject, + token.WithExpiry(options.TokenExpiry), + token.WithMetadata(sec.Metadata), + token.WithRoles(sec.Roles), + ) } diff --git a/auth/store/store_test.go b/auth/store/store_test.go new file mode 100644 index 00000000..03381e31 --- /dev/null +++ b/auth/store/store_test.go @@ -0,0 +1,287 @@ +package store + +import ( + "log" + "testing" + + "github.com/micro/go-micro/v2/auth" + memStore "github.com/micro/go-micro/v2/store/memory" +) + +func TestGenerate(t *testing.T) { + s := memStore.NewStore() + a := NewAuth(auth.Store(s)) + + id := "test" + roles := []string{"admin"} + metadata := map[string]string{"foo": "bar"} + + opts := []auth.GenerateOption{ + auth.WithRoles(roles), + auth.WithMetadata(metadata), + } + + // generate the account + acc, err := a.Generate(id, opts...) + if err != nil { + t.Fatalf("Generate returned an error: %v, expected nil", err) + } + // validate the account attributes were set correctly + if acc.ID != id { + t.Errorf("Generate returned %v as the ID, expected %v", acc.ID, id) + } + if len(acc.Roles) != len(roles) { + t.Errorf("Generate returned %v as the roles, expected %v", acc.Roles, roles) + } + if len(acc.Metadata) != len(metadata) { + t.Errorf("Generate returned %v as the metadata, expected %v", acc.Metadata, metadata) + } + + // validate the secret is valid + if _, err := a.Refresh(acc.Secret.Token); err != nil { + t.Errorf("Generate returned an invalid secret, error: %v", err) + } +} + +func TestGrant(t *testing.T) { + s := memStore.NewStore() + a := NewAuth(auth.Store(s)) + + res := &auth.Resource{Type: "service", Name: "Test", Endpoint: "Foo.Bar"} + if err := a.Grant("users.*", res); err != nil { + t.Fatalf("Grant returned an error: %v, expected nil", err) + } + + recs, err := s.List() + if err != nil { + t.Fatalf("Could not read from the store: %v", err) + } + if len(recs) != 1 { + t.Errorf("Expected Grant to write 1 record, actually wrote %v", len(recs)) + } +} + +func TestRevoke(t *testing.T) { + s := memStore.NewStore() + a := NewAuth(auth.Store(s)) + + res := &auth.Resource{Type: "service", Name: "Test", Endpoint: "Foo.Bar"} + if err := a.Grant("users.*", res); err != nil { + t.Fatalf("Grant returned an error: %v, expected nil", err) + } + + recs, err := s.List() + if err != nil { + t.Fatalf("Could not read from the store: %v", err) + } + if len(recs) != 1 { + t.Fatalf("Expected Grant to write 1 record, actually wrote %v", len(recs)) + } + + if err := a.Revoke("users.*", res); err != nil { + t.Fatalf("Revoke returned an error: %v, expected nil", err) + } + + recs, err = s.List() + if err != nil { + t.Fatalf("Could not read from the store: %v", err) + } + if len(recs) != 0 { + t.Fatalf("Expected Revoke to delete 1 record, actually deleted %v", 1-len(recs)) + } +} + +func TestInspect(t *testing.T) { + a := NewAuth() + + t.Run("Valid Token", func(t *testing.T) { + id := "test" + roles := []string{"admin"} + metadata := map[string]string{"foo": "bar"} + + opts := []auth.GenerateOption{ + auth.WithRoles(roles), + auth.WithMetadata(metadata), + } + + // generate and inspect the token + acc, err := a.Generate("test", opts...) + if err != nil { + log.Fatalf("Generate returned an error: %v, expected nil", err) + } + tok, err := a.Refresh(acc.Secret.Token) + if err != nil { + log.Fatalf("Refresh returned an error: %v, expected nil", err) + } + acc2, err := a.Inspect(tok.Token) + if err != nil { + log.Fatalf("Inspect returned an error: %v, expected nil", err) + } + + // validate the account attributes were retrieved correctly + if acc2.ID != id { + t.Errorf("Generate returned %v as the ID, expected %v", acc.ID, id) + } + if len(acc2.Roles) != len(roles) { + t.Errorf("Generate returned %v as the roles, expected %v", acc.Roles, roles) + } + if len(acc2.Metadata) != len(metadata) { + t.Errorf("Generate returned %v as the metadata, expected %v", acc.Metadata, metadata) + } + }) + + t.Run("Invalid Token", func(t *testing.T) { + _, err := a.Inspect("invalid token") + if err != auth.ErrInvalidToken { + t.Errorf("Inspect returned %v error, expected %v", err, auth.ErrInvalidToken) + } + }) +} + +func TestRefresh(t *testing.T) { + a := NewAuth() + + t.Run("Valid Secret", func(t *testing.T) { + roles := []string{"admin"} + metadata := map[string]string{"foo": "bar"} + + opts := []auth.GenerateOption{ + auth.WithRoles(roles), + auth.WithMetadata(metadata), + } + + // generate the account + acc, err := a.Generate("test", opts...) + if err != nil { + log.Fatalf("Generate returned an error: %v, expected nil", err) + } + + // refresh the token + tok, err := a.Refresh(acc.Secret.Token) + if err != nil { + log.Fatalf("Refresh returned an error: %v, expected nil", err) + } + + // validate the account attributes were set correctly + if acc.ID != tok.Subject { + t.Errorf("Refresh returned %v as the ID, expected %v", acc.ID, tok.Subject) + } + if len(acc.Roles) != len(tok.Roles) { + t.Errorf("Refresh returned %v as the roles, expected %v", acc.Roles, tok.Subject) + } + if len(acc.Metadata) != len(tok.Metadata) { + t.Errorf("Refresh returned %v as the metadata, expected %v", acc.Metadata, tok.Metadata) + } + }) + + t.Run("Invalid Secret", func(t *testing.T) { + _, err := a.Refresh("invalid secret") + if err != auth.ErrInvalidToken { + t.Errorf("Inspect returned %v error, expected %v", err, auth.ErrInvalidToken) + } + }) +} + +func TestVerify(t *testing.T) { + testRules := []struct { + Role string + Resource *auth.Resource + }{ + { + Role: "*", + Resource: &auth.Resource{Type: "service", Name: "go.micro.apps", Endpoint: "Apps.PublicList"}, + }, + { + Role: "user.*", + Resource: &auth.Resource{Type: "service", Name: "go.micro.apps", Endpoint: "Apps.List"}, + }, + { + Role: "user.developer", + Resource: &auth.Resource{Type: "service", Name: "go.micro.apps", Endpoint: "Apps.Update"}, + }, + { + Role: "admin", + Resource: &auth.Resource{Type: "service", Name: "go.micro.apps", Endpoint: "Apps.Delete"}, + }, + { + Role: "admin", + Resource: &auth.Resource{Type: "service", Name: "*", Endpoint: "*"}, + }, + } + + a := NewAuth() + for _, r := range testRules { + if err := a.Grant(r.Role, r.Resource); err != nil { + t.Fatalf("Grant returned an error: %v, expected nil", err) + } + } + + testTable := []struct { + Name string + Roles []string + Resource *auth.Resource + Error error + }{ + { + Name: "An account with no roles accessing a public endpoint", + Resource: &auth.Resource{Type: "service", Name: "go.micro.apps", Endpoint: "Apps.PublicList"}, + }, + { + Name: "An account with no roles accessing a private endpoint", + Resource: &auth.Resource{Type: "service", Name: "go.micro.apps", Endpoint: "Apps.Update"}, + Error: auth.ErrForbidden, + }, + { + Name: "An account with the user role accessing a user* endpoint", + Roles: []string{"user"}, + Resource: &auth.Resource{Type: "service", Name: "go.micro.apps", Endpoint: "Apps.List"}, + }, + { + Name: "An account with the user role accessing a user.admin endpoint", + Roles: []string{"user"}, + Resource: &auth.Resource{Type: "service", Name: "go.micro.apps", Endpoint: "Apps.Delete"}, + Error: auth.ErrForbidden, + }, + { + Name: "An account with the developer role accessing a user.developer endpoint", + Roles: []string{"user.developer"}, + Resource: &auth.Resource{Type: "service", Name: "go.micro.apps", Endpoint: "Apps.Update"}, + }, + { + Name: "An account with the developer role accessing an admin endpoint", + Roles: []string{"user.developer"}, + Resource: &auth.Resource{Type: "service", Name: "go.micro.apps", Endpoint: "Apps.Delete"}, + Error: auth.ErrForbidden, + }, + { + Name: "An admin account accessing an admin endpoint", + Roles: []string{"admin"}, + Resource: &auth.Resource{Type: "service", Name: "go.micro.apps", Endpoint: "Apps.Delete"}, + }, + { + Name: "An admin account accessing a generic service endpoint", + Roles: []string{"admin"}, + Resource: &auth.Resource{Type: "service", Name: "go.micro.foo", Endpoint: "Foo.Bar"}, + }, + { + Name: "An admin account accessing an unauthorised endpoint", + Roles: []string{"admin"}, + Resource: &auth.Resource{Type: "infra", Name: "go.micro.foo", Endpoint: "Foo.Bar"}, + Error: auth.ErrForbidden, + }, + { + Name: "A account with no roles accessing an unauthorised endpoint", + Resource: &auth.Resource{Type: "infra", Name: "go.micro.foo", Endpoint: "Foo.Bar"}, + Error: auth.ErrForbidden, + }, + } + + for _, tc := range testTable { + t.Run(tc.Name, func(t *testing.T) { + acc := &auth.Account{Roles: tc.Roles} + if err := a.Verify(acc, tc.Resource); err != tc.Error { + t.Errorf("Verify returned %v error, expected %v", err, tc.Error) + } + }) + } +} diff --git a/auth/token/basic/basic.go b/auth/token/basic/basic.go new file mode 100644 index 00000000..edf5d2fb --- /dev/null +++ b/auth/token/basic/basic.go @@ -0,0 +1,95 @@ +package basic + +import ( + "encoding/json" + "time" + + "github.com/google/uuid" + "github.com/micro/go-micro/v2/auth" + "github.com/micro/go-micro/v2/auth/token" + "github.com/micro/go-micro/v2/store" +) + +// Basic implementation of token provider, backed by the store +type Basic struct { + store store.Store +} + +// NewTokenProvider returns an initialized basic provider +func NewTokenProvider(opts ...token.Option) token.Provider { + options := token.NewOptions(opts...) + + if options.Store == nil { + options.Store = store.DefaultStore + } + + return &Basic{ + store: options.Store, + } +} + +// Generate a token for an account +func (b *Basic) Generate(subject string, opts ...token.GenerateOption) (*auth.Token, error) { + options := token.NewGenerateOptions(opts...) + + // construct the token + token := auth.Token{ + Subject: subject, + Type: b.String(), + Token: uuid.New().String(), + Created: time.Now(), + Expiry: time.Now().Add(options.Expiry), + Metadata: options.Metadata, + Roles: options.Roles, + } + + // marshal the account to bytes + bytes, err := json.Marshal(token) + if err != nil { + return nil, err + } + + // write to the store + err = b.store.Write(&store.Record{ + Key: token.Token, + Value: bytes, + Expiry: options.Expiry, + }) + if err != nil { + return nil, err + } + + // return the token + return &token, nil +} + +// Inspect a token +func (b *Basic) Inspect(t string) (*auth.Token, error) { + // lookup the token in the store + recs, err := b.store.Read(t) + if err == store.ErrNotFound { + return nil, token.ErrInvalidToken + } else if err != nil { + return nil, err + } + bytes := recs[0].Value + + // unmarshal the bytes + var tok *auth.Token + if err := json.Unmarshal(bytes, &tok); err != nil { + return nil, err + } + + // ensure the token hasn't expired, the store should + // expire the token but we're checking again + if tok.Expiry.Unix() < time.Now().Unix() { + return nil, token.ErrInvalidToken + } + + return tok, err +} + +// String returns basic +func (b *Basic) String() string { + return "basic" +} diff --git a/auth/token/basic/basic_test.go b/auth/token/basic/basic_test.go new file mode 100644 index 00000000..159387a1 --- /dev/null +++ b/auth/token/basic/basic_test.go @@ -0,0 +1,80 @@ +package basic + +import ( + "testing" + "time" + + "github.com/micro/go-micro/v2/auth/token" + "github.com/micro/go-micro/v2/store/memory" +) + +func TestGenerate(t *testing.T) { + store := memory.NewStore() + b := NewTokenProvider(token.WithStore(store)) + + _, err := b.Generate("test") + if err != nil { + t.Fatalf("Generate returned %v error, expected nil", err) + } + + recs, err := store.List() + if err != nil { + t.Fatalf("Unable to read from store: %v", err) + } + if len(recs) != 1 { + t.Errorf("Generate didn't write to the store, expected 1 record, got %v", len(recs)) + } +} + +func TestInspect(t *testing.T) { + store := memory.NewStore() + b := NewTokenProvider(token.WithStore(store)) + + t.Run("Valid token", func(t *testing.T) { + md := map[string]string{"foo": "bar"} + roles := []string{"admin"} + subject := "test" + + opts := []token.GenerateOption{ + token.WithMetadata(md), + token.WithRoles(roles), + } + + tok, err := b.Generate(subject, opts...) + if err != nil { + t.Fatalf("Generate returned %v error, expected nil", err) + } + + tok2, err := b.Inspect(tok.Token) + if err != nil { + t.Fatalf("Inspect returned %v error, expected nil", err) + } + if tok2.Subject != subject { + t.Errorf("Inspect returned %v as the token subject, expected %v", tok2.Subject, subject) + } + if len(tok2.Roles) != len(roles) { + t.Errorf("Inspect returned %v roles, expected %v", len(tok2.Roles), len(roles)) + } + if len(tok2.Metadata) != len(md) { + t.Errorf("Inspect returned %v as the token metadata, expected %v", tok2.Metadata, md) + } + }) + + t.Run("Expired token", func(t *testing.T) { + tok, err := b.Generate("foo", token.WithExpiry(-10*time.Second)) + if err != nil { + t.Fatalf("Generate returned %v error, expected nil", err) + } + + if _, err = b.Inspect(tok.Token); err != token.ErrInvalidToken { + t.Fatalf("Inspect returned %v error, expected %v", err, token.ErrInvalidToken) + } + }) + + t.Run("Invalid token", func(t *testing.T) { + _, err := b.Inspect("Invalid token") + if err != token.ErrInvalidToken { + t.Fatalf("Inspect returned %v error, expected %v", err, token.ErrInvalidToken) + } + }) +} diff --git a/auth/token/jwt/jwt.go b/auth/token/jwt/jwt.go new file mode 100644 index 00000000..12504fbe --- /dev/null +++ b/auth/token/jwt/jwt.go @@ -0,0 +1,111 @@ +package jwt + +import ( + "encoding/base64" + "time" + + "github.com/dgrijalva/jwt-go" + "github.com/micro/go-micro/v2/auth" + "github.com/micro/go-micro/v2/auth/token" +) + +// authClaims to be encoded in the JWT +type authClaims struct { + Roles []string `json:"roles"` + Metadata map[string]string `json:"metadata"` + + jwt.StandardClaims +} + +// JWT implementation of token provider +type JWT struct { + opts token.Options +} + +// NewTokenProvider returns an initialized basic provider +func NewTokenProvider(opts ...token.Option) token.Provider { + return &JWT{ + opts: token.NewOptions(opts...), + } +} + +// Generate a new JWT +func (j *JWT) Generate(subject string, opts ...token.GenerateOption) (*auth.Token, error) { + // decode the private key + priv, err := base64.StdEncoding.DecodeString(j.opts.PrivateKey) + if err != nil { + return nil, err + } + + // parse the private key + key, err := jwt.ParseRSAPrivateKeyFromPEM(priv) + if err != nil { + return nil, token.ErrEncodingToken + } + + // parse the options + options := token.NewGenerateOptions(opts...) + + // generate the JWT + expiry := time.Now().Add(options.Expiry) + t := jwt.NewWithClaims(jwt.SigningMethodRS256, authClaims{ + options.Roles, options.Metadata, jwt.StandardClaims{ + Subject: subject, + ExpiresAt: expiry.Unix(), + }, + }) + tok, err := t.SignedString(key) + if err != nil { + return nil, err + } + + // return the token + return &auth.Token{ + Subject: subject, + Token: tok, + Type: j.String(), + Created: time.Now(), + Expiry: expiry, + Roles: options.Roles, + Metadata: options.Metadata, + }, nil +} + +// Inspect a JWT +func (j *JWT) Inspect(t string) (*auth.Token, error) { + // decode the public key + pub, err := base64.StdEncoding.DecodeString(j.opts.PublicKey) + if err != nil { + return nil, err + } + + // parse the public key + res, err := jwt.ParseWithClaims(t, &authClaims{}, func(token *jwt.Token) (interface{}, error) { + return jwt.ParseRSAPublicKeyFromPEM(pub) + }) + if err != nil { + return nil, token.ErrInvalidToken + } + + // validate the token + if !res.Valid { + return nil, token.ErrInvalidToken + } + claims, ok := res.Claims.(*authClaims) + if !ok { + return nil, token.ErrInvalidToken + } + + // return the token + return &auth.Token{ + Token: t, + Subject: claims.Subject, + Metadata: claims.Metadata, + Roles: claims.Roles, + }, nil +} + +// String returns JWT +func (j *JWT) String() string { + return "jwt" +} diff --git a/auth/token/jwt/jwt_test.go b/auth/token/jwt/jwt_test.go new file mode 100644 index 00000000..3a2c6237 --- /dev/null +++ b/auth/token/jwt/jwt_test.go @@ -0,0 +1,90 @@ +package jwt + +import ( + "io/ioutil" + "testing" + "time" + + "github.com/micro/go-micro/v2/auth/token" +) + +func TestGenerate(t *testing.T) { + privKey, err := ioutil.ReadFile("test/sample_key") + if err != nil { + t.Fatalf("Unable to read private key: %v", err) + } + + j := NewTokenProvider( + token.WithPrivateKey(string(privKey)), + ) + + _, err = j.Generate("test") + if err != nil { + t.Fatalf("Generate returned %v error, expected nil", err) + } +} + +func TestInspect(t *testing.T) { + pubKey, err := ioutil.ReadFile("test/sample_key.pub") + if err != nil { + t.Fatalf("Unable to read public key: %v", err) + } + privKey, err := ioutil.ReadFile("test/sample_key") + if err != nil { + t.Fatalf("Unable to read private key: %v", err) + } + + j := NewTokenProvider( + token.WithPublicKey(string(pubKey)), + token.WithPrivateKey(string(privKey)), + ) + + t.Run("Valid token", func(t *testing.T) { + md := map[string]string{"foo": "bar"} + roles := []string{"admin"} + subject := "test" + + opts := []token.GenerateOption{ + token.WithMetadata(md), + token.WithRoles(roles), + } + + tok, err := j.Generate(subject, opts...) + if err != nil { + t.Fatalf("Generate returned %v error, expected nil", err) + } + + tok2, err := j.Inspect(tok.Token) + if err != nil { + t.Fatalf("Inspect returned %v error, expected nil", err) + } + if tok2.Subject != subject { + t.Errorf("Inspect returned %v as the token subject, expected %v", tok2.Subject, subject) + } + if len(tok2.Roles) != len(roles) { + t.Errorf("Inspect returned %v roles, expected %v", len(tok2.Roles), len(roles)) + } + if len(tok2.Metadata) != len(md) { + t.Errorf("Inspect returned %v as the token metadata, expected %v", tok2.Metadata, md) + } + }) + + t.Run("Expired token", func(t *testing.T) { + tok, err := j.Generate("foo", token.WithExpiry(-10*time.Second)) + if err != nil { + t.Fatalf("Generate returned %v error, expected nil", err) + } + + if _, err = j.Inspect(tok.Token); err != token.ErrInvalidToken { + t.Fatalf("Inspect returned %v error, expected %v", err, token.ErrInvalidToken) + } + }) + + t.Run("Invalid token", func(t *testing.T) { + _, err := j.Inspect("Invalid token") + if err != token.ErrInvalidToken { + t.Fatalf("Inspect returned %v error, expected %v", err, token.ErrInvalidToken) + } + }) + +} diff --git a/auth/token/jwt/test/sample_key b/auth/token/jwt/test/sample_key new file mode 100644 index 00000000..25488667 --- /dev/null +++ b/auth/token/jwt/test/sample_key @@ -0,0 +1 @@ +LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlKS3dJQkFBS0NBZ0VBOFNiSlA1WGJFaWRSbTViMnNOcExHbzJlV2ZVNU9KZTBpemdySHdEOEg3RjZQa1BkCi9SbDkvMXBNVjdNaU8zTEh3dGhIQzJCUllxcisxd0Zkb1pDR0JZckxhWHVYRnFLMHZ1WmhQcUUzYXpqdUlIUXUKMEJIL2xYUU1xeUVxRjVNSTJ6ZWpDNHpNenIxNU9OK2dFNEpuaXBqcC9DZGpPUEFEbUpHK0JKOXFlRS9RUGVtLwptVWRJVC9MYUY3a1F4eVlLNVZLbitOZ09Xek1sektBQXBDbjdUVEtCVWU4RlpHNldTWDdMVjBlTEdIc29pYnhsCm85akRqbFk1b0JPY3pmcWVOV0hLNUdYQjdRd3BMTmg5NDZQelpucW9hcFdVZStZL1JPaUhpekpUY3I1Wk1TTDUKd2xFcThoTmhtaG01Tk5lL08rR2dqQkROU2ZVaDA2K3E0bmdtYm1OWDVoODM4QmJqUmN5YzM2ZHd6NkpVK2R1bwpSdFFoZ2lZOTEwcFBmOWJhdVhXcXdVQ1VhNHFzSHpqS1IwTC9OMVhYQXlsQ0RqeWVnWnp6Y093MkNIOFNrZkZVCnJnTHJQYkVCOWVnY0drMzgrYnBLczNaNlJyNSt0bkQxQklQSUZHTGVJMFVPQzAreGlCdjBvenhJRE9GbldhOVUKVEdEeFV4OG9qOFZJZVJuV0RxNk1jMWlKcDhVeWNpQklUUnR3NGRabzcweG1mbmVJV3pyM0tTTmFoU29nSmRSMApsYVF6QXVQM2FpV1hJTXAyc2M4U2MrQmwrTGpYbUJveEJyYUJIaDlLa0pKRWNnQUZ3czJib2pDbEpPWXhvRi9YCmdGS1NzSW5IRHJIVk95V1BCZTNmYWRFYzc3YituYi9leE96cjFFcnhoR2c5akZtcmtPK3M0eEdodjZNQ0F3RUEKQVFLQ0FnRUFqUzc1Q2VvUlRRcUtBNzZaaFNiNGEzNVlKRENtcEpSazFsRTNKYnFzNFYxRnhXaDBjZmJYeG9VMgpSdTRRYjUrZWhsdWJGSFQ2a1BxdG9uRWhRVExjMUNmVE9WbHJOb3hocDVZM2ZyUmlQcnNnNXcwK1R3RUtrcFJUCnltanJQTXdQbGxCM2U0NmVaYmVXWGc3R3FFVmptMGcxVFRRK0tocVM4R0w3VGJlTFhRN1ZTem9ydTNCNVRKMVEKeEN6TVB0dnQ2eDYrU3JrcmhvZG1iT3VNRkpDam1TbWxmck9pZzQ4Zkc3NUpERHRObXpLWHBEUVJpYUNodFJhVQpQRHpmUTlTamhYdFFqdkZvWFFFT3BqdkZVRjR2WldNUWNQNUw1VklDM3JRSWp4MFNzQTN6S0FwakVUbjJHNjN2CktZby8zVWttbzhkUCtGRHA3NCs5a3pLNHFFaFJycEl3bEtiN0VOZWtDUXZqUFl1K3pyKzMyUXdQNTJ2L2FveWQKdjJJaUY3M2laTU1vZDhhYjJuQStyVEI2T0cvOVlSYk5kV21tay9VTi9jUHYrN214TmZ6Y1d1ZU1XcThxMXh4eAptNTNpR0NSQ29PQ1lDQk4zcUFkb1JwYW5xd3lCOUxrLzFCQjBHUld3MjgxK3VhNXNYRnZBVDBKeTVURnduMncvClU1MlJKWFlNOXVhMFBvd214b0RDUWRuNFZYVkdNZGdXaHN4aXhHRlYwOUZObWJJQWJaN0xaWGtkS1gzc1ZVbTcKWU1WYWIzVVo2bEhtdXYzT1NzcHNVUlRqN1hiRzZpaVVlaDU1aW91OENWbnRndWtFcnEzQTQwT05FVzhjNDBzOQphVTBGaSs4eWZpQTViaVZHLzF0bWlucUVERkhuQStnWk1xNEhlSkZxcWZxaEZKa1JwRGtDZ2dFQkFQeGR1NGNKCm5Da1duZDdPWFlHMVM3UDdkVWhRUzgwSDlteW9uZFc5bGFCQm84RWRPeTVTZzNOUmsxQ2pNZFZ1a3FMcjhJSnkKeStLWk15SVpvSlJvbllaMEtIUUVMR3ZLbzFOS2NLQ1FJbnYvWHVCdFJpRzBVb1pQNVkwN0RpRFBRQWpYUjlXUwpBc0EzMmQ1eEtFOC91Y3h0MjVQVzJFakNBUmtVeHQ5d0tKazN3bC9JdXVYRlExTDdDWjJsOVlFUjlHeWxUbzhNCmxXUEY3YndtUFV4UVNKaTNVS0FjTzZweTVUU1lkdWQ2aGpQeXJwSXByNU42VGpmTlRFWkVBeU9LbXVpOHVkUkoKMUg3T3RQVEhGZElKQjNrNEJnRDZtRE1HbjB2SXBLaDhZN3NtRUZBbFkvaXlCZjMvOHk5VHVMb1BycEdqR3RHbgp4Y2RpMHFud2p0SGFNbFVDZ2dFQkFQU2Z0dVFCQ2dTU2JLUSswUEFSR2VVeEQyTmlvZk1teENNTmdHUzJ5Ull3CjRGaGV4ZWkwMVJoaFk1NjE3UjduR1dzb0czd1RQa3dvRTJtbE1aQkoxeWEvUU9RRnQ3WG02OVl0RGh0T2FWbDgKL0o4dlVuSTBtWmxtT2pjTlRoYnVPZDlNSDlRdGxIRUMxMlhYdHJNb3Fsb0U2a05TT0pJalNxYm9wcDRXc1BqcApvZTZ0Nkdyd1RhOHBHeUJWWS90Mi85Ym5ORHVPVlpjODBaODdtY2gzcDNQclBqU3h5di9saGxYMFMwYUdHTkhTCk1XVjdUa25OaGo1TWlIRXFnZ1pZemtBWTkyd1JoVENnU1A2M0VNcitUWXFudXVuMXJHbndPYm95TDR2aFRpV0UKcU42UDNCTFlCZ1FpMllDTDludEJrOEl6RHZyd096dW5GVnhhZ0g5SVVoY0NnZ0VCQUwzQXlLa1BlOENWUmR6cQpzL284VkJDZmFSOFhhUGRnSGxTek1BSXZpNXEwNENqckRyMlV3MHZwTVdnM1hOZ0xUT3g5bFJpd3NrYk9SRmxHCmhhd3hRUWlBdkk0SE9WTlBTU0R1WHVNTG5USTQ0S0RFNlMrY2cxU0VMS2pWbDVqcDNFOEpkL1RJMVpLc0xBQUsKZTNHakM5UC9ZbE8xL21ndW4xNjVkWk01cFAwWHBPb2FaeFV2RHFFTktyekR0V1g0RngyOTZlUzdaSFJodFpCNwovQ2t1VUhlcmxrN2RDNnZzdWhTaTh2eTM3c0tPbmQ0K3c4cVM4czhZYVZxSDl3ZzVScUxxakp0bmJBUnc3alVDCm9KQ053M1hNdnc3clhaYzRTbnhVQUNMRGJNV2lLQy9xL1ZGWW9oTEs2WkpUVkJscWd5cjBSYzBRWmpDMlNJb0kKMjRwRWt3VUNnZ0VCQUpqb0FJVVNsVFY0WlVwaExXN3g4WkxPa01UWjBVdFFyd2NPR0hSYndPUUxGeUNGMVFWNQppejNiR2s4SmZyZHpVdk1sTmREZm9uQXVHTHhQa3VTVEUxWlg4L0xVRkJveXhyV3dvZ0cxaUtwME11QTV6em90CjROai9DbUtCQVkvWnh2anA5M2RFS21aZGxWQkdmeUFMeWpmTW5MWUovZXh5L09YSnhPUktZTUttSHg4M08zRWsKMWhvb0FwbTZabTIzMjRGME1iVU1ham5Idld2ZjhHZGJTNk5zcHd4L0dkbk1tYVMrdUJMVUhVMkNLbmc1bEIwVAp4OWJITmY0dXlPbTR0dXRmNzhCd1R5V3UreEdrVW0zZ2VZMnkvR1hqdDZyY2l1ajFGNzFDenZzcXFmZThTcDdJCnd6SHdxcTNzVHR5S2lCYTZuYUdEYWpNR1pKYSt4MVZJV204Q2dnRUJBT001ajFZR25Ba0pxR0czQWJSVDIvNUMKaVVxN0loYkswOGZsSGs5a2YwUlVjZWc0ZVlKY3dIRXJVaE4rdWQyLzE3MC81dDYra0JUdTVZOUg3bkpLREtESQpoeEg5SStyamNlVkR0RVNTRkluSXdDQ1lrOHhOUzZ0cHZMV1U5b0pibGFKMlZsalV2NGRFWGVQb0hkREh1Zk9ZClVLa0lsV2E3Uit1QzNEOHF5U1JrQnFLa3ZXZ1RxcFNmTVNkc1ZTeFIzU2Q4SVhFSHFjTDNUNEtMWGtYNEdEamYKMmZOSTFpZkx6ekhJMTN3Tk5IUTVRNU9SUC9pell2QzVzZkx4U2ZIUXJiMXJZVkpKWkI5ZjVBUjRmWFpHSVFsbApjMG8xd0JmZFlqMnZxVDlpR09IQnNSSTlSL2M2RzJQcUt3aFRpSzJVR2lmVFNEUVFuUkF6b2tpQVkrbE8vUjQ9Ci0tLS0tRU5EIFJTQSBQUklWQVRFIEtFWS0tLS0tCg== \ No newline at end of file diff --git a/auth/token/jwt/test/sample_key.pub b/auth/token/jwt/test/sample_key.pub new file mode 100644 index 00000000..77bd153d --- /dev/null +++ b/auth/token/jwt/test/sample_key.pub @@ -0,0 +1 @@ +LS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS0KTUlJQ0lqQU5CZ2txaGtpRzl3MEJBUUVGQUFPQ0FnOEFNSUlDQ2dLQ0FnRUE4U2JKUDVYYkVpZFJtNWIyc05wTApHbzJlV2ZVNU9KZTBpemdySHdEOEg3RjZQa1BkL1JsOS8xcE1WN01pTzNMSHd0aEhDMkJSWXFyKzF3RmRvWkNHCkJZckxhWHVYRnFLMHZ1WmhQcUUzYXpqdUlIUXUwQkgvbFhRTXF5RXFGNU1JMnplakM0ek16cjE1T04rZ0U0Sm4KaXBqcC9DZGpPUEFEbUpHK0JKOXFlRS9RUGVtL21VZElUL0xhRjdrUXh5WUs1VktuK05nT1d6TWx6S0FBcENuNwpUVEtCVWU4RlpHNldTWDdMVjBlTEdIc29pYnhsbzlqRGpsWTVvQk9jemZxZU5XSEs1R1hCN1F3cExOaDk0NlB6ClpucW9hcFdVZStZL1JPaUhpekpUY3I1Wk1TTDV3bEVxOGhOaG1obTVOTmUvTytHZ2pCRE5TZlVoMDYrcTRuZ20KYm1OWDVoODM4QmJqUmN5YzM2ZHd6NkpVK2R1b1J0UWhnaVk5MTBwUGY5YmF1WFdxd1VDVWE0cXNIempLUjBMLwpOMVhYQXlsQ0RqeWVnWnp6Y093MkNIOFNrZkZVcmdMclBiRUI5ZWdjR2szOCticEtzM1o2UnI1K3RuRDFCSVBJCkZHTGVJMFVPQzAreGlCdjBvenhJRE9GbldhOVVUR0R4VXg4b2o4VkllUm5XRHE2TWMxaUpwOFV5Y2lCSVRSdHcKNGRabzcweG1mbmVJV3pyM0tTTmFoU29nSmRSMGxhUXpBdVAzYWlXWElNcDJzYzhTYytCbCtMalhtQm94QnJhQgpIaDlLa0pKRWNnQUZ3czJib2pDbEpPWXhvRi9YZ0ZLU3NJbkhEckhWT3lXUEJlM2ZhZEVjNzdiK25iL2V4T3pyCjFFcnhoR2c5akZtcmtPK3M0eEdodjZNQ0F3RUFBUT09Ci0tLS0tRU5EIFBVQkxJQyBLRVktLS0tLQo= \ No newline at end of file diff --git a/auth/token/options.go b/auth/token/options.go new file mode 100644 index 00000000..d2e490b8 --- /dev/null +++ b/auth/token/options.go @@ -0,0 +1,96 @@ +package token + +import ( + "time" + + "github.com/micro/go-micro/v2/store" +) + +type Options struct { + // Store to persist the tokens + Store store.Store + // PublicKey base64 encoded, used by JWT + PublicKey string + // PrivateKey base64 encoded, used by JWT + PrivateKey string +} + +type Option func(o *Options) + +// WithStore sets the token providers store +func WithStore(s store.Store) Option { + return func(o *Options) { + o.Store = s + } +} + +// WithPublicKey sets the JWT public key +func WithPublicKey(key string) Option { + return func(o *Options) { + o.PublicKey = key + } +} + +// WithPrivateKey sets the JWT private key +func WithPrivateKey(key string) Option { + return func(o *Options) { + o.PrivateKey = key + } +} + +func NewOptions(opts ...Option) Options { + var options Options + for _, o := range opts { + o(&options) + } + //set default store + if options.Store == nil { + options.Store = store.DefaultStore + } + return options +} + +type GenerateOptions struct { + // Expiry for the token + Expiry time.Duration + // Metadata associated with the account + Metadata map[string]string + // Roles/scopes associated with the account + Roles []string +} + +type GenerateOption func(o *GenerateOptions) + +// WithExpiry for the generated account's token expires +func WithExpiry(d time.Duration) GenerateOption { + return func(o *GenerateOptions) { + o.Expiry = d + } +} + +// WithMetadata for the token +func WithMetadata(md map[string]string) func(o *GenerateOptions) { + return func(o *GenerateOptions) { + o.Metadata = md + } +} + +// WithRoles for the token +func WithRoles(rs []string) func(o *GenerateOptions) { + return func(o *GenerateOptions) { + o.Roles = rs + } +} + +// NewGenerateOptions from a slice of options +func NewGenerateOptions(opts ...GenerateOption) GenerateOptions { + var options GenerateOptions + for _, o := range opts { + o(&options) + } + //set default Expiry of token + if options.Expiry == 0 { + options.Expiry = time.Minute * 15 + } + return options +} diff --git a/auth/token/token.go b/auth/token/token.go new file mode 100644 index 00000000..da8409f1 --- /dev/null +++ b/auth/token/token.go @@ -0,0 +1,23 @@ +package token + +import ( + "errors" + + "github.com/micro/go-micro/v2/auth" +) + +var ( + // ErrNotFound is returned when a token cannot be found + ErrNotFound = errors.New("token not found") + // ErrEncodingToken is returned when the service encounters an error during encoding + ErrEncodingToken = errors.New("error encoding the token") + // ErrInvalidToken is returned when the token provided is not valid + ErrInvalidToken = errors.New("invalid token provided") +) + +// Provider generates and inspects tokens +type Provider interface { + Generate(subject string, opts ...GenerateOption) (*auth.Token, error) + Inspect(token string) (*auth.Token, error) + String() string +} diff --git a/config/cmd/cmd.go b/config/cmd/cmd.go index a43a4886..4ea999ca 100644 --- a/config/cmd/cmd.go +++ b/config/cmd/cmd.go @@ -70,8 +70,7 @@ import ( memTracer "github.com/micro/go-micro/v2/debug/trace/memory" // auth - jwtAuth "github.com/micro/go-micro/v2/auth/jwt" - sAuth "github.com/micro/go-micro/v2/auth/service" + svcAuth "github.com/micro/go-micro/v2/auth/service" storeAuth "github.com/micro/go-micro/v2/auth/store" // auth providers @@ -271,11 +270,6 @@ var ( EnvVars: []string{"MICRO_AUTH_PRIVATE_KEY"}, Usage: "Private key for JWT auth (base64 encoded PEM)", }, - &cli.StringSliceFlag{ - Name: "auth_exclude", - EnvVars: []string{"MICRO_AUTH_EXCLUDE"}, - Usage: "Comma-separated list of endpoints excluded from authentication, e.g. Users.ListUsers", - }, &cli.StringFlag{ Name: "auth_provider", EnvVars: []string{"MICRO_AUTH_PROVIDER"}, @@ -365,9 +359,8 @@ var ( } DefaultAuths = map[string]func(...auth.Option) auth.Auth{ - "service": sAuth.NewAuth, + "service": svcAuth.NewAuth, "store": storeAuth.NewAuth, - "jwt": jwtAuth.NewAuth, } DefaultAuthProviders = map[string]func(...provider.Option) provider.Provider{ @@ -665,7 +658,7 @@ func (c *cmd) Before(ctx *cli.Context) error { } if len(ctx.String("auth_token")) > 0 { - authOpts = append(authOpts, auth.Token(ctx.String("auth_token"))) + authOpts = append(authOpts, auth.ServiceToken(ctx.String("auth_token"))) } if len(ctx.String("auth_public_key")) > 0 { @@ -676,10 +669,6 @@ func (c *cmd) Before(ctx *cli.Context) error { authOpts = append(authOpts, auth.PrivateKey(ctx.String("auth_private_key"))) } - if len(ctx.StringSlice("auth_exclude")) > 0 { - authOpts = append(authOpts, auth.Exclude(ctx.StringSlice("auth_exclude")...)) - } - if name := ctx.String("auth_provider"); len(name) > 0 { p, ok := DefaultAuthProviders[name] if !ok { @@ -707,9 +696,7 @@ func (c *cmd) Before(ctx *cli.Context) error { } if len(authOpts) > 0 { - if err := (*c.opts.Auth).Init(authOpts...); err != nil { - logger.Fatalf("Error configuring auth: %v", err) - } + (*c.opts.Auth).Init(authOpts...) } if ctx.String("config") == "service" { diff --git a/service.go b/service.go index a47a38de..a693aa34 100644 --- a/service.go +++ b/service.go @@ -118,7 +118,7 @@ func (s *service) Init(opts ...Option) { // 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)) + s.opts.Auth.Init(auth.ServiceToken(tk)) } }) } diff --git a/util/wrapper/wrapper.go b/util/wrapper/wrapper.go index 80dc702b..cd4cc30c 100644 --- a/util/wrapper/wrapper.go +++ b/util/wrapper/wrapper.go @@ -164,6 +164,11 @@ func AuthHandler(fn func() auth.Auth) server.HandlerWrapper { return h(ctx, req, rsp) } + // Check for auth service endpoints which should be excluded from auth + if strings.HasPrefix(req.Endpoint(), "Auth.") { + return h(ctx, req, rsp) + } + // Extract the token if present. Note: if noop is being used // then the token can be blank without erroring var token string @@ -177,28 +182,15 @@ func AuthHandler(fn func() auth.Auth) server.HandlerWrapper { } // Verify the token - account, authErr := a.Verify(token) - - // If there is an account, set it in the context - if authErr == nil { - var err error - ctx, err = auth.ContextWithAccount(ctx, account) - - if err != nil { - return err - } + account, err := a.Inspect(token) + if err != nil { + return errors.Unauthorized("go.micro.auth", err.Error()) } - // Return if the user disabled auth on this endpoint - for _, e := range a.Options().Exclude { - if e == req.Endpoint() { - return h(ctx, req, rsp) - } - } - - // If the authErr is set, prevent the user from calling the endpoint - if authErr != nil { - return errors.Unauthorized("go.micro.auth", authErr.Error()) + // There is an account, set it in the context + ctx, err = auth.ContextWithAccount(ctx, account) + if err != nil { + return err } // The user is authorised, allow the call