Add Rule to Auth interface

This commit is contained in:
Ben Toogood 2020-05-20 11:59:01 +01:00
parent dc10f88c12
commit f6d9416a9e
8 changed files with 296 additions and 311 deletions

View File

@ -7,16 +7,13 @@ import (
"time" "time"
) )
// BearerScheme used for Authorization header
const BearerScheme = "Bearer "
var ( var (
// ErrNotFound is returned when a resouce cannot be found // ErrInvalidToken is when the token provided is not valid
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") ErrInvalidToken = errors.New("invalid token provided")
// ErrInvalidRole is returned when the role provided was invalid // ErrForbidden is when a user does not have the necessary roles or scoeps to access a resource
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") ErrForbidden = errors.New("resource forbidden")
) )
@ -28,30 +25,22 @@ type Auth interface {
Options() Options Options() Options
// Generate a new account // Generate a new account
Generate(id string, opts ...GenerateOption) (*Account, error) Generate(id string, opts ...GenerateOption) (*Account, error)
// Grant access to a resource // Verify an account has access to a resource using the rules
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 Verify(acc *Account, res *Resource) error
// Inspect a token // Inspect a token
Inspect(token string) (*Account, error) Inspect(token string) (*Account, error)
// Token generated using refresh token // Token generated using refresh token or credentials
Token(opts ...TokenOption) (*Token, error) Token(opts ...TokenOption) (*Token, error)
// Grant access to a resource
Grant(rule *Rule) error
// Revoke access to a resource
Revoke(rule *Rule) error
// Rules returns all the rules used to verify requests
Rules() ([]*Rule, error)
// String returns the name of the implementation // String returns the name of the implementation
String() string String() string
} }
// Resource is an entity such as a user or
type Resource struct {
// Name of the resource
Name string `json:"name"`
// Type of resource, e.g.
Type string `json:"type"`
// Endpoint resource e.g NotesService.Create
Endpoint string `json:"endpoint"`
}
// Account provided by an auth provider // Account provided by an auth provider
type Account struct { type Account struct {
// ID of the account e.g. email // ID of the account e.g. email
@ -112,13 +101,47 @@ type Token struct {
Expiry time.Time `json:"expiry"` Expiry time.Time `json:"expiry"`
} }
// Expired returns a boolean indicating if the token needs to be refreshed
func (t *Token) Expired() bool {
return t.Expiry.Unix() < time.Now().Unix()
}
// Resource is an entity such as a user or
type Resource struct {
// Name of the resource, e.g. go.micro.service.notes
Name string `json:"name"`
// Type of resource, e.g. service
Type string `json:"type"`
// Endpoint resource e.g NotesService.Create
Endpoint string `json:"endpoint"`
}
// Access defines the type of access a rule grants
type Access int
const ( const (
// TokenCookieName is the name of the cookie which stores the auth token // AccessGranted to a resource
TokenCookieName = "micro-token" AccessGranted Access = iota
// BearerScheme used for Authorization header // AccessDenied to a resource
BearerScheme = "Bearer " AccessDenied
) )
// Rule is used to verify access to a resource
type Rule struct {
// ID of the rule, e.g. "public"
ID string
// Role the rule requires, a blank role indicates open to the public and * indicates the rule
// applies to any valid account
Role string
// Resource the rule applies to
Resource *Resource
// Access determines if the rule grants or denies access to the resource
Access Access
// Priority the rule should take when verifying a request, the higher the value the sooner the
// rule will be applied
Priority int32
}
type accountKey struct{} type accountKey struct{}
// AccountFromContext gets the account from the context, which // AccountFromContext gets the account from the context, which

View File

@ -58,15 +58,20 @@ func (n *noop) Generate(id string, opts ...GenerateOption) (*Account, error) {
} }
// Grant access to a resource // Grant access to a resource
func (n *noop) Grant(role string, res *Resource) error { func (n *noop) Grant(rule *Rule) error {
return nil return nil
} }
// Revoke access to a resource // Revoke access to a resource
func (n *noop) Revoke(role string, res *Resource) error { func (n *noop) Revoke(rule *Rule) error {
return nil return nil
} }
// Rules used to verify requests
func (n *noop) Rules() ([]*Rule, error) {
return []*Rule{}, nil
}
// Verify an account has access to a resource // Verify an account has access to a resource
func (n *noop) Verify(acc *Account, res *Resource) error { func (n *noop) Verify(acc *Account, res *Resource) error {
return nil return nil

View File

@ -1,11 +1,11 @@
package jwt package jwt
import ( import (
"fmt"
"sync" "sync"
"time" "time"
"github.com/micro/go-micro/v2/auth" "github.com/micro/go-micro/v2/auth"
"github.com/micro/go-micro/v2/auth/rules"
"github.com/micro/go-micro/v2/auth/token" "github.com/micro/go-micro/v2/auth/token"
jwtToken "github.com/micro/go-micro/v2/auth/token/jwt" jwtToken "github.com/micro/go-micro/v2/auth/token/jwt"
) )
@ -25,7 +25,7 @@ type rule struct {
type jwt struct { type jwt struct {
options auth.Options options auth.Options
jwt token.Provider jwt token.Provider
rules []*rule rules []*auth.Rule
sync.Mutex sync.Mutex
} }
@ -77,84 +77,38 @@ func (j *jwt) Generate(id string, opts ...auth.GenerateOption) (*auth.Account, e
return account, nil return account, nil
} }
func (j *jwt) Grant(role string, res *auth.Resource) error { func (j *jwt) Grant(rule *auth.Rule) error {
j.Lock() j.Lock()
defer j.Unlock() defer j.Unlock()
j.rules = append(j.rules, &rule{role, res}) j.rules = append(j.rules, rule)
return nil return nil
} }
func (j *jwt) Revoke(role string, res *auth.Resource) error { func (j *jwt) Revoke(rule *auth.Rule) error {
j.Lock() j.Lock()
defer j.Unlock() defer j.Unlock()
rules := make([]*rule, 0, len(j.rules)) rules := []*auth.Rule{}
var ruleFound bool
for _, r := range rules { for _, r := range rules {
if r.role == role && r.resource == res { if r.ID != rule.ID {
ruleFound = true
} else {
rules = append(rules, r) rules = append(rules, r)
} }
} }
if !ruleFound {
return auth.ErrNotFound
}
j.rules = rules j.rules = rules
return nil return nil
} }
func (j *jwt) Verify(acc *auth.Account, res *auth.Resource) error { func (j *jwt) Verify(acc *auth.Account, res *auth.Resource) error {
// check the scope
scope := "namespace." + j.options.Namespace
if acc != nil && !acc.HasScope(scope) {
return fmt.Errorf("Missing required scope: %v", scope)
}
j.Lock() j.Lock()
rules := j.rules defer j.Unlock()
j.Unlock() return rules.Verify(j.options.Namespace, j.rules, acc, res)
for _, rule := range rules {
// validate the rule applies to the requested resource
if rule.resource.Type != "*" && rule.resource.Type != res.Type {
continue
}
if rule.resource.Name != "*" && rule.resource.Name != res.Name {
continue
}
if rule.resource.Endpoint != "*" && rule.resource.Endpoint != res.Endpoint {
continue
} }
// a blank role indicates anyone can access the resource, even without an account func (j *jwt) Rules() ([]*auth.Rule, error) {
if rule.role == "" { j.Lock()
return nil defer j.Unlock()
} return j.rules, nil
// all furter checks require an account
if acc == nil {
continue
}
// this rule allows any account access, allow the request
if rule.role == "*" {
return nil
}
// if the account has the necessary role, allow the request
for _, r := range acc.Roles {
if r == rule.role {
return nil
}
}
}
// no rules matched, forbid the request
return auth.ErrForbidden
} }
func (j *jwt) Inspect(token string) (*auth.Account, error) { func (j *jwt) Inspect(token string) (*auth.Account, error) {

99
auth/rules/rules.go Normal file
View File

@ -0,0 +1,99 @@
package rules
import (
"fmt"
"sort"
"strings"
"github.com/micro/go-micro/v2/auth"
)
// Verify an account has access to a resource using the rules provided. If the account does not have
// access an error will be returned. If there are no rules provided which match the resource, an error
// will be returned
func Verify(namespace string, rules []*auth.Rule, acc *auth.Account, res *auth.Resource) error {
// ensure the account has the necessary scope. Some rules allow for public access so we don't
// error if the account is nil.
if acc != nil && !acc.HasScope("namespace."+namespace) {
return fmt.Errorf("Missing required scope: %v", "namespace."+namespace)
}
// the rule is only to be applied if the type matches the resource or is catch-all (*)
validTypes := []string{"*", res.Type}
// the rule is only to be applied if the name matches the resource or is catch-all (*)
validNames := []string{"*", res.Name}
// rules can have wildcard excludes on endpoints since this can also be a path for web services,
// e.g. /foo/* would include /foo/bar. We also want to check for wildcards and the exact endpoint
validEndpoints := []string{"*", res.Endpoint}
if comps := strings.Split(res.Endpoint, "/"); len(comps) > 1 {
for i := 1; i < len(comps); i++ {
wildcard := fmt.Sprintf("%v/*", strings.Join(comps[0:i], "/"))
validEndpoints = append(validEndpoints, wildcard)
}
}
// filter the rules to the ones which match the criteria above
filteredRules := make([]*auth.Rule, 0)
for _, rule := range rules {
if !include(validTypes, rule.Resource.Type) {
continue
}
if !include(validNames, rule.Resource.Name) {
continue
}
if !include(validEndpoints, rule.Resource.Endpoint) {
continue
}
filteredRules = append(filteredRules, rule)
}
// sort the filtered rules by priority, highest to lowest
sort.SliceStable(filteredRules, func(i, j int) bool {
return filteredRules[i].Priority > filteredRules[j].Priority
})
// loop through the rules and check for a rule which applies to this account
for _, rule := range filteredRules {
// a blank role indicates the rule applies to everyone, even nil accounts
if rule.Role == "" && rule.Access == auth.AccessDenied {
return auth.ErrForbidden
} else if rule.Role == "" && rule.Access == auth.AccessGranted {
return nil
}
// all furter checks require an account
if acc == nil {
continue
}
// this rule applies to any account
if rule.Role == "*" && rule.Access == auth.AccessDenied {
return auth.ErrForbidden
} else if rule.Role == "" && rule.Access == auth.AccessGranted {
return nil
}
// if the account has the necessary role
if include(acc.Roles, rule.Role) && rule.Access == auth.AccessDenied {
return auth.ErrForbidden
} else if rule.Role == "" && rule.Access == auth.AccessGranted {
return nil
}
}
// if no rules matched then return forbidden
return auth.ErrForbidden
}
// include is a helper function which checks to see if the slice contains the value. includes is
// not case sensitive.
func include(slice []string, val string) bool {
for _, s := range slice {
if strings.ToLower(s) == strings.ToLower(val) {
return true
}
}
return false
}

View File

@ -861,10 +861,7 @@ func (m *Rule) GetPriority() int32 {
} }
type CreateRequest struct { type CreateRequest struct {
Role string `protobuf:"bytes,1,opt,name=role,proto3" json:"role,omitempty"` Rule *Rule `protobuf:"bytes,1,opt,name=rule,proto3" json:"rule,omitempty"`
Resource *Resource `protobuf:"bytes,2,opt,name=resource,proto3" json:"resource,omitempty"`
Access Access `protobuf:"varint,3,opt,name=access,proto3,enum=go.micro.auth.Access" json:"access,omitempty"`
Priority int32 `protobuf:"varint,4,opt,name=priority,proto3" json:"priority,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"` XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"` XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"` XXX_sizecache int32 `json:"-"`
@ -895,34 +892,13 @@ func (m *CreateRequest) XXX_DiscardUnknown() {
var xxx_messageInfo_CreateRequest proto.InternalMessageInfo var xxx_messageInfo_CreateRequest proto.InternalMessageInfo
func (m *CreateRequest) GetRole() string { func (m *CreateRequest) GetRule() *Rule {
if m != nil { if m != nil {
return m.Role return m.Rule
}
return ""
}
func (m *CreateRequest) GetResource() *Resource {
if m != nil {
return m.Resource
} }
return nil return nil
} }
func (m *CreateRequest) GetAccess() Access {
if m != nil {
return m.Access
}
return Access_UNKNOWN
}
func (m *CreateRequest) GetPriority() int32 {
if m != nil {
return m.Priority
}
return 0
}
type CreateResponse struct { type CreateResponse struct {
XXX_NoUnkeyedLiteral struct{} `json:"-"` XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"` XXX_unrecognized []byte `json:"-"`
@ -955,10 +931,7 @@ func (m *CreateResponse) XXX_DiscardUnknown() {
var xxx_messageInfo_CreateResponse proto.InternalMessageInfo var xxx_messageInfo_CreateResponse proto.InternalMessageInfo
type DeleteRequest struct { type DeleteRequest struct {
Role string `protobuf:"bytes,1,opt,name=role,proto3" json:"role,omitempty"` Rule *Rule `protobuf:"bytes,1,opt,name=rule,proto3" json:"rule,omitempty"`
Resource *Resource `protobuf:"bytes,2,opt,name=resource,proto3" json:"resource,omitempty"`
Access Access `protobuf:"varint,3,opt,name=access,proto3,enum=go.micro.auth.Access" json:"access,omitempty"`
Priority int32 `protobuf:"varint,4,opt,name=priority,proto3" json:"priority,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"` XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"` XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"` XXX_sizecache int32 `json:"-"`
@ -989,34 +962,13 @@ func (m *DeleteRequest) XXX_DiscardUnknown() {
var xxx_messageInfo_DeleteRequest proto.InternalMessageInfo var xxx_messageInfo_DeleteRequest proto.InternalMessageInfo
func (m *DeleteRequest) GetRole() string { func (m *DeleteRequest) GetRule() *Rule {
if m != nil { if m != nil {
return m.Role return m.Rule
}
return ""
}
func (m *DeleteRequest) GetResource() *Resource {
if m != nil {
return m.Resource
} }
return nil return nil
} }
func (m *DeleteRequest) GetAccess() Access {
if m != nil {
return m.Access
}
return Access_UNKNOWN
}
func (m *DeleteRequest) GetPriority() int32 {
if m != nil {
return m.Priority
}
return 0
}
type DeleteResponse struct { type DeleteResponse struct {
XXX_NoUnkeyedLiteral struct{} `json:"-"` XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"` XXX_unrecognized []byte `json:"-"`
@ -1149,63 +1101,63 @@ func init() {
func init() { proto.RegisterFile("auth/service/proto/auth.proto", fileDescriptor_21300bfacc51fc2a) } func init() { proto.RegisterFile("auth/service/proto/auth.proto", fileDescriptor_21300bfacc51fc2a) }
var fileDescriptor_21300bfacc51fc2a = []byte{ var fileDescriptor_21300bfacc51fc2a = []byte{
// 892 bytes of a gzipped FileDescriptorProto // 888 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xcc, 0x56, 0xdd, 0x8e, 0xdb, 0x44, 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xac, 0x56, 0xdd, 0x8e, 0xdb, 0x44,
0x14, 0x5e, 0xff, 0xc4, 0xc9, 0x9e, 0xc4, 0xd9, 0x68, 0xba, 0x2d, 0x96, 0xcb, 0x96, 0xad, 0x8b, 0x14, 0x5e, 0xff, 0xc4, 0xc9, 0x9e, 0xc4, 0xbb, 0xd1, 0x74, 0x5b, 0xac, 0x94, 0x2d, 0x5b, 0x17,
0xd0, 0x52, 0x41, 0x16, 0xa5, 0x37, 0x40, 0x6f, 0x58, 0x35, 0x51, 0x68, 0xa1, 0x41, 0x58, 0x45, 0xc1, 0x52, 0x41, 0x16, 0xb9, 0x37, 0x0b, 0xbd, 0x61, 0xd5, 0x44, 0xa1, 0x85, 0x06, 0x61, 0x15,
0xe5, 0x06, 0x55, 0xc6, 0x39, 0xb0, 0xd6, 0x66, 0x6d, 0x33, 0x33, 0x5e, 0x91, 0x1b, 0x24, 0x5e, 0x95, 0x1b, 0x54, 0x19, 0xe7, 0xc0, 0x5a, 0x9b, 0xb5, 0xcd, 0xcc, 0x38, 0x22, 0x37, 0x48, 0xbc,
0x80, 0x47, 0xe0, 0x86, 0x3b, 0x9e, 0x89, 0x7b, 0x5e, 0x03, 0x79, 0x7e, 0xbc, 0xb1, 0xe3, 0x54, 0x00, 0x8f, 0xc0, 0x03, 0xf0, 0x4c, 0xdc, 0xf3, 0x1a, 0xc8, 0xf3, 0xe3, 0x8d, 0x1d, 0xa7, 0xaa,
0x05, 0x7a, 0xd1, 0xbb, 0x39, 0x33, 0x67, 0xce, 0x7c, 0xdf, 0x77, 0x7e, 0x6c, 0x38, 0x8a, 0x0a, 0x0a, 0x77, 0x73, 0x66, 0xce, 0xf9, 0xe6, 0x7c, 0xdf, 0x39, 0x73, 0x6c, 0x38, 0x8e, 0x0a, 0x7e,
0x7e, 0x7e, 0xca, 0x90, 0x5e, 0x25, 0x31, 0x9e, 0xe6, 0x34, 0xe3, 0xd9, 0x69, 0xb9, 0x35, 0x16, 0x79, 0xc6, 0x90, 0xae, 0x92, 0x18, 0xcf, 0x72, 0x9a, 0xf1, 0xec, 0xac, 0xdc, 0x1a, 0x8b, 0x25,
0x4b, 0xe2, 0xfe, 0x98, 0x8d, 0x2f, 0x93, 0x98, 0x66, 0xe3, 0x72, 0x33, 0xb8, 0x09, 0x37, 0xbe, 0x71, 0x7f, 0xce, 0xc6, 0xd7, 0x49, 0x4c, 0xb3, 0x71, 0xb9, 0xe9, 0xdf, 0x86, 0x5b, 0x5f, 0x27,
0x4c, 0x18, 0x3f, 0x8b, 0xe3, 0xac, 0x48, 0x39, 0x0b, 0xf1, 0xa7, 0x02, 0x19, 0x0f, 0x9e, 0xc0, 0x8c, 0x5f, 0xc4, 0x71, 0x56, 0xa4, 0x9c, 0x85, 0xf8, 0x4b, 0x81, 0x8c, 0xfb, 0xcf, 0xe0, 0xa8,
0x61, 0x7d, 0x9b, 0xe5, 0x59, 0xca, 0x90, 0x4c, 0xa0, 0x17, 0xa9, 0x3d, 0xcf, 0x38, 0xb6, 0x4e, 0xbe, 0xcd, 0xf2, 0x2c, 0x65, 0x48, 0x02, 0xe8, 0x45, 0x6a, 0xcf, 0x33, 0x4e, 0xac, 0xd3, 0x7e,
0xfa, 0x93, 0x5b, 0xe3, 0x5a, 0xc0, 0xb1, 0xba, 0x12, 0x56, 0x7e, 0xc1, 0xaf, 0x06, 0x74, 0x9e, 0x70, 0x67, 0x5c, 0x03, 0x1c, 0xab, 0x90, 0xb0, 0xf2, 0xf3, 0x7f, 0x37, 0xa0, 0xf3, 0x22, 0xbb,
0x65, 0x17, 0x98, 0x92, 0xbb, 0x30, 0x88, 0xe2, 0x18, 0x19, 0x7b, 0xc1, 0x4b, 0xdb, 0x33, 0x8e, 0xc2, 0x94, 0xdc, 0x87, 0x41, 0x14, 0xc7, 0xc8, 0xd8, 0x2b, 0x5e, 0xda, 0x9e, 0x71, 0x62, 0x9c,
0x8d, 0x93, 0xfd, 0xb0, 0x2f, 0xf7, 0xa4, 0xcb, 0x3d, 0x70, 0x29, 0xfe, 0x40, 0x91, 0x9d, 0x2b, 0xee, 0x87, 0x7d, 0xb9, 0x27, 0x5d, 0x1e, 0x80, 0x4b, 0xf1, 0x27, 0x8a, 0xec, 0x52, 0xf9, 0x98,
0x1f, 0x53, 0xf8, 0x0c, 0xd4, 0xa6, 0x74, 0xf2, 0xa0, 0x1b, 0x53, 0x8c, 0x38, 0x2e, 0x3d, 0xeb, 0xc2, 0x67, 0xa0, 0x36, 0xa5, 0x93, 0x07, 0xdd, 0x98, 0x62, 0xc4, 0x71, 0xe1, 0x59, 0x27, 0xc6,
0xd8, 0x38, 0xb1, 0x42, 0x6d, 0x92, 0x5b, 0xe0, 0xe0, 0xcf, 0x79, 0x42, 0xd7, 0x9e, 0x2d, 0x0e, 0xa9, 0x15, 0x6a, 0x93, 0xdc, 0x01, 0x07, 0x7f, 0xcd, 0x13, 0xba, 0xf6, 0x6c, 0x71, 0xa0, 0x2c,
0x94, 0x15, 0xfc, 0x66, 0x42, 0x57, 0x21, 0x23, 0x43, 0x30, 0x93, 0xa5, 0x7a, 0xdb, 0x4c, 0x96, 0xff, 0x0f, 0x13, 0xba, 0x2a, 0x33, 0x72, 0x00, 0x66, 0xb2, 0x50, 0x77, 0x9b, 0xc9, 0x82, 0x10,
0x84, 0x80, 0xcd, 0xd7, 0x39, 0xaa, 0x97, 0xc4, 0x9a, 0x1c, 0x42, 0x87, 0x66, 0x2b, 0x64, 0x9e, 0xb0, 0xf9, 0x3a, 0x47, 0x75, 0x93, 0x58, 0x93, 0x23, 0xe8, 0xd0, 0x6c, 0x89, 0xcc, 0xb3, 0x4e,
0x75, 0x6c, 0x9d, 0xec, 0x87, 0xd2, 0x20, 0x9f, 0x41, 0xef, 0x12, 0x79, 0xb4, 0x8c, 0x78, 0xe4, 0xac, 0xd3, 0xfd, 0x50, 0x1a, 0xe4, 0x0b, 0xe8, 0x5d, 0x23, 0x8f, 0x16, 0x11, 0x8f, 0x3c, 0x5b,
0xd9, 0x82, 0xfd, 0xbb, 0xed, 0xec, 0xc7, 0x4f, 0x95, 0xdb, 0x2c, 0xe5, 0x74, 0x1d, 0x56, 0xb7, 0xb0, 0x7f, 0xbf, 0x9d, 0xfd, 0xf8, 0xb9, 0x72, 0x9b, 0xa6, 0x9c, 0xae, 0xc3, 0x2a, 0xaa, 0xcc,
0x4a, 0x7c, 0x2c, 0xce, 0x72, 0x64, 0x5e, 0x47, 0x04, 0x56, 0x16, 0xf1, 0xa1, 0x97, 0xd3, 0xec, 0x8f, 0xc5, 0x59, 0x8e, 0xcc, 0xeb, 0x08, 0x60, 0x65, 0x91, 0x11, 0xf4, 0x72, 0x9a, 0xad, 0x92,
0x2a, 0x59, 0x22, 0xf5, 0x1c, 0x81, 0xa3, 0xb2, 0xc5, 0x1d, 0x8c, 0x29, 0x72, 0xaf, 0x2b, 0x4e, 0x05, 0x52, 0xcf, 0x11, 0x79, 0x54, 0xb6, 0x88, 0xc1, 0x98, 0x22, 0xf7, 0xba, 0xe2, 0x44, 0x59,
0x94, 0xe5, 0x3f, 0x04, 0xb7, 0xf6, 0x0c, 0x19, 0x81, 0x75, 0x81, 0x6b, 0xc5, 0xac, 0x5c, 0x96, 0xa3, 0xc7, 0xe0, 0xd6, 0xae, 0x21, 0x43, 0xb0, 0xae, 0x70, 0xad, 0x98, 0x95, 0xcb, 0x92, 0xc6,
0x34, 0xae, 0xa2, 0x55, 0xa1, 0xb9, 0x49, 0xe3, 0x53, 0xf3, 0x63, 0x23, 0x58, 0x40, 0x2f, 0x44, 0x2a, 0x5a, 0x16, 0x9a, 0x9b, 0x34, 0x3e, 0x37, 0xcf, 0x0d, 0x7f, 0x0e, 0xbd, 0x10, 0x59, 0x56,
0x96, 0x15, 0x34, 0xc6, 0x52, 0x80, 0x34, 0xba, 0x44, 0x75, 0x51, 0xac, 0x5b, 0x45, 0xf1, 0xa1, 0xd0, 0x18, 0x4b, 0x01, 0xd2, 0xe8, 0x1a, 0x55, 0xa0, 0x58, 0xb7, 0x8a, 0x32, 0x82, 0x1e, 0xa6,
0x87, 0xe9, 0x32, 0xcf, 0x92, 0x94, 0x0b, 0xdd, 0xf7, 0xc3, 0xca, 0x0e, 0x7e, 0x37, 0xe1, 0x60, 0x8b, 0x3c, 0x4b, 0x52, 0x2e, 0x74, 0xdf, 0x0f, 0x2b, 0xdb, 0xff, 0xd3, 0x84, 0xc3, 0x19, 0xa6,
0x8e, 0x29, 0xd2, 0x88, 0xa3, 0x2a, 0xa2, 0x2d, 0xa1, 0x2b, 0x51, 0xcd, 0x4d, 0x51, 0x3f, 0xdf, 0x48, 0x23, 0x8e, 0xaa, 0x89, 0xb6, 0x84, 0xae, 0x44, 0x35, 0x37, 0x45, 0xfd, 0x72, 0x43, 0x54,
0x10, 0xd5, 0x12, 0xa2, 0x7e, 0xd0, 0x10, 0xb5, 0x11, 0xf7, 0x15, 0xc4, 0xb5, 0x6b, 0xe2, 0x5e, 0x4b, 0x88, 0xfa, 0x71, 0x43, 0xd4, 0x06, 0xee, 0x1b, 0x88, 0x6b, 0xd7, 0xc4, 0xbd, 0x11, 0xb0,
0x0b, 0xd8, 0xd9, 0x14, 0xb0, 0xe2, 0xe8, 0xd4, 0x39, 0x56, 0x89, 0xe8, 0xd6, 0x13, 0xf1, 0xff, 0xb3, 0x29, 0x60, 0xc5, 0xd1, 0xa9, 0x73, 0xac, 0x0a, 0xd1, 0xad, 0x17, 0xe2, 0xbf, 0x09, 0x3e,
0x04, 0x9f, 0xc2, 0xe8, 0x9a, 0x87, 0xea, 0xa6, 0x8f, 0xa0, 0xab, 0xba, 0x44, 0xc4, 0xd8, 0xdd, 0x81, 0xe1, 0x0d, 0x0f, 0xf5, 0x9a, 0x3e, 0x85, 0xae, 0x7a, 0x25, 0x02, 0x63, 0xf7, 0x63, 0xd2,
0x4c, 0xda, 0x2d, 0x78, 0x0e, 0x83, 0x39, 0x8d, 0x52, 0xae, 0x25, 0x26, 0x60, 0x97, 0x2a, 0xea, 0x6e, 0xfe, 0x4b, 0x18, 0xcc, 0x68, 0x94, 0x72, 0x2d, 0x31, 0x01, 0xbb, 0x54, 0x51, 0x97, 0xae,
0xd4, 0x95, 0x6b, 0xf2, 0x00, 0x7a, 0x54, 0xa5, 0x56, 0xc0, 0xe8, 0x4f, 0xde, 0x6a, 0x84, 0xd5, 0x5c, 0x93, 0x47, 0xd0, 0xa3, 0xaa, 0xb4, 0x22, 0x8d, 0x7e, 0xf0, 0x4e, 0x03, 0x56, 0x57, 0x3e,
0x99, 0x0f, 0x2b, 0xc7, 0xe0, 0x00, 0x5c, 0x15, 0x58, 0x62, 0x0b, 0xbe, 0x05, 0x37, 0xc4, 0xab, 0xac, 0x1c, 0xfd, 0x43, 0x70, 0x15, 0xb0, 0xcc, 0xcd, 0xff, 0x1e, 0xdc, 0x10, 0x57, 0xd9, 0x15,
0xec, 0x02, 0x5f, 0xfb, 0x53, 0x23, 0x18, 0xea, 0xc8, 0xea, 0xad, 0xf7, 0x60, 0xf8, 0x38, 0x65, 0xfe, 0xef, 0x57, 0x0d, 0xe1, 0x40, 0x23, 0xab, 0xbb, 0x3e, 0x80, 0x83, 0xa7, 0x29, 0xcb, 0x31,
0x39, 0xc6, 0x15, 0xaf, 0x43, 0xe8, 0x6c, 0x8e, 0x08, 0x69, 0x04, 0x8f, 0xe0, 0xa0, 0xf2, 0xfb, 0xae, 0x78, 0x1d, 0x41, 0x67, 0x73, 0x44, 0x48, 0xc3, 0x7f, 0x02, 0x87, 0x95, 0xdf, 0x5b, 0x4b,
0xcf, 0x12, 0xfe, 0x02, 0x03, 0x31, 0x45, 0x76, 0x55, 0xe9, 0x75, 0xb5, 0x98, 0xb5, 0x6a, 0xd9, 0xf8, 0x1b, 0x0c, 0xc4, 0x14, 0xd9, 0xd5, 0xa5, 0x37, 0xdd, 0x62, 0xd6, 0xba, 0x65, 0x6b, 0x32,
0x9a, 0x4c, 0x56, 0xcb, 0x64, 0xba, 0x0b, 0x03, 0x71, 0xf8, 0xa2, 0x36, 0x85, 0xfa, 0x62, 0x6f, 0x59, 0x2d, 0x93, 0xe9, 0x3e, 0x0c, 0xc4, 0xe1, 0xab, 0xda, 0x14, 0xea, 0x8b, 0xbd, 0xa9, 0x1c,
0x26, 0x47, 0xd1, 0x43, 0x70, 0xd5, 0xfb, 0x8a, 0xc2, 0xfd, 0x4d, 0xae, 0xfd, 0xc9, 0x61, 0x83, 0x45, 0x8f, 0xc1, 0x55, 0xf7, 0x2b, 0x0a, 0x0f, 0x37, 0xb9, 0xf6, 0x83, 0xa3, 0x06, 0x01, 0xe9,
0x80, 0x74, 0x56, 0x0a, 0xfc, 0x69, 0x80, 0x1d, 0x16, 0x2b, 0x6c, 0x1b, 0x62, 0x22, 0x3b, 0xe6, 0xac, 0x14, 0xf8, 0xcb, 0x00, 0x3b, 0x2c, 0x96, 0xd8, 0x36, 0xc4, 0x44, 0x75, 0xcc, 0x1d, 0xd5,
0x8e, 0xec, 0x58, 0xaf, 0x98, 0x1d, 0xf2, 0x21, 0x38, 0x72, 0x1e, 0x0b, 0xec, 0xc3, 0xc9, 0xcd, 0xb1, 0xde, 0xb0, 0x3a, 0xe4, 0x13, 0x70, 0xe4, 0x3c, 0x16, 0xb9, 0x1f, 0x04, 0xb7, 0xb7, 0xf5,
0x6d, 0x3d, 0x91, 0xb1, 0x50, 0x39, 0xc9, 0x7e, 0x49, 0x32, 0x9a, 0xf0, 0xb5, 0xe8, 0xae, 0x4e, 0x44, 0xc6, 0x42, 0xe5, 0x24, 0xdf, 0x4b, 0x92, 0xd1, 0x84, 0xaf, 0xc5, 0xeb, 0xea, 0x84, 0x95,
0x58, 0xd9, 0xc1, 0x1f, 0x06, 0xb8, 0x8f, 0xc4, 0x60, 0x7e, 0xdd, 0x35, 0xb4, 0x81, 0xd2, 0xfa, 0xed, 0x9f, 0x83, 0xfb, 0x44, 0xcc, 0x65, 0x2d, 0xf5, 0x87, 0x60, 0xd3, 0x42, 0xb5, 0x50, 0x3f,
0xb7, 0x28, 0xed, 0x06, 0xca, 0x11, 0x0c, 0x35, 0x48, 0x55, 0x8e, 0x25, 0xee, 0x29, 0xae, 0xf0, 0xb8, 0xd5, 0x4c, 0xa6, 0x58, 0x62, 0x28, 0x1c, 0xca, 0x16, 0xd1, 0x91, 0xaa, 0x45, 0xce, 0xc1,
0x8d, 0xc7, 0xad, 0x41, 0x2a, 0xdc, 0x2e, 0xf4, 0xcb, 0x8f, 0xb6, 0xfe, 0x86, 0x7f, 0x02, 0x03, 0x9d, 0xe0, 0x12, 0xdf, 0x0e, 0x4b, 0x47, 0x2a, 0x2c, 0x17, 0xfa, 0xe5, 0xc7, 0x4d, 0x7f, 0xeb,
0x69, 0xaa, 0x3a, 0x7b, 0x1f, 0x3a, 0xb4, 0x28, 0xc7, 0xaf, 0xfc, 0x70, 0xdf, 0x68, 0xa2, 0x2d, 0x3e, 0x83, 0x81, 0x34, 0x55, 0x3d, 0x3e, 0x82, 0x4e, 0x19, 0xa8, 0x3f, 0x70, 0xad, 0xd0, 0xd2,
0x56, 0x18, 0x4a, 0x8f, 0xfb, 0x63, 0x70, 0x24, 0x12, 0xd2, 0x87, 0xee, 0x37, 0x8b, 0x2f, 0x16, 0xe3, 0xe1, 0x18, 0x1c, 0xa9, 0x07, 0xe9, 0x43, 0xf7, 0xbb, 0xf9, 0x57, 0xf3, 0x6f, 0x5e, 0xce,
0x5f, 0x3d, 0x5f, 0x8c, 0xf6, 0x4a, 0x63, 0x1e, 0x9e, 0x2d, 0x9e, 0xcd, 0xa6, 0x23, 0x83, 0x00, 0x87, 0x7b, 0xa5, 0x31, 0x0b, 0x2f, 0xe6, 0x2f, 0xa6, 0x93, 0xa1, 0x41, 0x00, 0x9c, 0xc9, 0x74,
0x38, 0xd3, 0xd9, 0xe2, 0xf1, 0x6c, 0x3a, 0x32, 0x27, 0x7f, 0x1b, 0x60, 0x9f, 0x15, 0xfc, 0x9c, 0xfe, 0x74, 0x3a, 0x19, 0x9a, 0xc1, 0x3f, 0x06, 0xd8, 0x17, 0x05, 0xbf, 0x24, 0xcf, 0xa1, 0xa7,
0x3c, 0x85, 0x9e, 0x9e, 0x72, 0xe4, 0xce, 0xcb, 0xc7, 0xb8, 0xff, 0xce, 0xce, 0x73, 0xc5, 0x67, 0xa7, 0x01, 0xb9, 0xf7, 0xfa, 0x71, 0x37, 0x7a, 0x6f, 0xe7, 0xb9, 0xe2, 0xb3, 0x47, 0x9e, 0x41,
0x8f, 0x3c, 0x81, 0xae, 0x6a, 0x78, 0x72, 0xd4, 0xf0, 0xae, 0x0f, 0x0c, 0xff, 0xce, 0xae, 0xe3, 0x57, 0x3d, 0x0c, 0x72, 0xdc, 0xf0, 0xae, 0x3f, 0xac, 0xd1, 0xbd, 0x5d, 0xc7, 0x15, 0xd6, 0x44,
0x2a, 0xd6, 0x54, 0xff, 0x85, 0xdc, 0x6e, 0x6d, 0x30, 0x15, 0xe7, 0xed, 0xf6, 0x43, 0x1d, 0x65, 0x7f, 0xad, 0xef, 0xb6, 0x36, 0xa2, 0xc2, 0x79, 0xb7, 0xfd, 0x50, 0xa3, 0x04, 0x3f, 0x40, 0x4f,
0xf2, 0x1d, 0xf4, 0xf4, 0x4f, 0x11, 0xf9, 0x1a, 0xec, 0x52, 0x60, 0x12, 0x34, 0xee, 0xb4, 0xfc, 0xff, 0x3c, 0x90, 0x6f, 0xc1, 0x2e, 0x05, 0x26, 0x7e, 0x23, 0xa6, 0xe5, 0xc7, 0x63, 0xf4, 0xe0,
0x50, 0xf9, 0xf7, 0x5e, 0xea, 0x53, 0x85, 0xff, 0xcb, 0x80, 0x4e, 0x99, 0x08, 0x46, 0xe6, 0xe0, 0xb5, 0x3e, 0x15, 0xfc, 0xdf, 0x06, 0x74, 0xca, 0x42, 0x30, 0x32, 0x03, 0x47, 0xb6, 0x0a, 0x69,
0xc8, 0xb2, 0x24, 0x4d, 0x48, 0xb5, 0x96, 0xf2, 0x8f, 0x76, 0x9c, 0x56, 0xbc, 0xe7, 0xe0, 0xc8, 0xa6, 0x54, 0xeb, 0xbd, 0xd1, 0xf1, 0x8e, 0xd3, 0x8a, 0xf7, 0x0c, 0x1c, 0xd9, 0x27, 0x5b, 0x40,
0x3a, 0xd9, 0x0a, 0x54, 0xab, 0xf1, 0xad, 0x40, 0x8d, 0xe2, 0xda, 0x23, 0x67, 0x8a, 0xae, 0xdf, 0xb5, 0xc6, 0xdb, 0x02, 0x6a, 0x34, 0xd7, 0x1e, 0xb9, 0x50, 0x74, 0x47, 0x2d, 0x54, 0x34, 0xc8,
0x42, 0x45, 0x07, 0xb9, 0xdd, 0x7a, 0xa6, 0x43, 0x7c, 0xef, 0x88, 0x7f, 0xd0, 0x07, 0xff, 0x04, 0xdd, 0xd6, 0x33, 0x0d, 0xf1, 0xa3, 0x23, 0xfe, 0xd5, 0x1e, 0xfd, 0x1b, 0x00, 0x00, 0xff, 0xff,
0x00, 0x00, 0xff, 0xff, 0x60, 0xd4, 0x97, 0x04, 0xa4, 0x0a, 0x00, 0x00, 0x7f, 0x20, 0xd5, 0xb8, 0xcc, 0x09, 0x00, 0x00,
} }
// Reference imports to suppress errors if they are not otherwise used. // Reference imports to suppress errors if they are not otherwise used.

View File

@ -110,19 +110,13 @@ message Rule {
} }
message CreateRequest { message CreateRequest {
string role = 1; Rule rule = 1;
Resource resource = 2;
Access access = 3;
int32 priority = 4;
} }
message CreateResponse {} message CreateResponse {}
message DeleteRequest { message DeleteRequest {
string role = 1; Rule rule = 1;
Resource resource = 2;
Access access = 3;
int32 priority = 4;
} }
message DeleteResponse {} message DeleteResponse {}

View File

@ -2,13 +2,12 @@ package service
import ( import (
"context" "context"
"fmt"
"sort"
"strings" "strings"
"sync" "sync"
"time" "time"
"github.com/micro/go-micro/v2/auth" "github.com/micro/go-micro/v2/auth"
"github.com/micro/go-micro/v2/auth/rules"
pb "github.com/micro/go-micro/v2/auth/service/proto" 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"
"github.com/micro/go-micro/v2/auth/token/jwt" "github.com/micro/go-micro/v2/auth/token/jwt"
@ -23,8 +22,7 @@ type svc struct {
auth pb.AuthService auth pb.AuthService
rule pb.RulesService rule pb.RulesService
jwt token.Provider jwt token.Provider
rules []*auth.Rule
rules []*pb.Rule
sync.Mutex sync.Mutex
} }
@ -79,84 +77,53 @@ func (s *svc) Generate(id string, opts ...auth.GenerateOption) (*auth.Account, e
} }
// Grant access to a resource // Grant access to a resource
func (s *svc) Grant(role string, res *auth.Resource) error { func (s *svc) Grant(rule *auth.Rule) error {
_, err := s.rule.Create(context.TODO(), &pb.CreateRequest{ _, err := s.rule.Create(context.TODO(), &pb.CreateRequest{
Role: role, Rule: &pb.Rule{
Id: rule.ID,
Role: rule.Role,
Priority: rule.Priority,
Access: pb.Access_GRANTED, Access: pb.Access_GRANTED,
Resource: &pb.Resource{ Resource: &pb.Resource{
Type: res.Type, Type: rule.Resource.Type,
Name: res.Name, Name: rule.Resource.Name,
Endpoint: res.Endpoint, Endpoint: rule.Resource.Endpoint,
},
}, },
}) })
go s.loadRules()
return err return err
} }
// Revoke access to a resource // Revoke access to a resource
func (s *svc) Revoke(role string, res *auth.Resource) error { func (s *svc) Revoke(rule *auth.Rule) error {
_, err := s.rule.Delete(context.TODO(), &pb.DeleteRequest{ _, err := s.rule.Delete(context.TODO(), &pb.DeleteRequest{
Role: role, Rule: &pb.Rule{
Id: rule.ID,
Role: rule.Role,
Priority: rule.Priority,
Access: pb.Access_GRANTED, Access: pb.Access_GRANTED,
Resource: &pb.Resource{ Resource: &pb.Resource{
Type: res.Type, Type: rule.Resource.Type,
Name: res.Name, Name: rule.Resource.Name,
Endpoint: res.Endpoint, Endpoint: rule.Resource.Endpoint,
},
}, },
}) })
go s.loadRules()
return err return err
} }
func (s *svc) Rules() ([]*auth.Rule, error) {
return s.rules, nil
}
// Verify an account has access to a resource // Verify an account has access to a resource
func (s *svc) Verify(acc *auth.Account, res *auth.Resource) error { func (s *svc) Verify(acc *auth.Account, res *auth.Resource) error {
// check the scope
scope := "namespace." + s.options.Namespace
if acc != nil && !acc.HasScope(scope) {
return fmt.Errorf("Missing required scope: %v", scope)
}
// load the rules if none are loaded // load the rules if none are loaded
s.loadRulesIfEmpty() s.loadRulesIfEmpty()
// verify the request using the rules
queries := [][]string{ return rules.Verify(s.options.Namespace, s.rules, acc, res)
{res.Type, res.Name, res.Endpoint}, // check for specific role, e.g. service.foo.ListFoo:admin (role is checked in accessForRule)
{res.Type, res.Name, "*"}, // check for wildcard endpoint, e.g. service.foo*
{res.Type, "*"}, // check for wildcard name, e.g. service.*
{"*"}, // check for wildcard type, e.g. *
}
// endpoint is a url which can have wildcard excludes, e.g.
// "/foo/*" will allow "/foo/bar"
if comps := strings.Split(res.Endpoint, "/"); len(comps) > 1 {
for i := 1; i < len(comps); i++ {
wildcard := fmt.Sprintf("%v/*", strings.Join(comps[0:i], "/"))
queries = append(queries, []string{res.Type, res.Name, wildcard})
}
}
// set a default account id / namespace to log
logID := acc.ID
if len(logID) == 0 {
logID = "[no account]"
}
for _, q := range queries {
for _, rule := range s.listRules(q...) {
switch accessForRule(rule, acc, res) {
case pb.Access_UNKNOWN:
continue // rule did not specify access, check the next rule
case pb.Access_GRANTED:
log.Tracef("%v granted access to %v:%v:%v by rule %v", logID, res.Type, res.Name, res.Endpoint, rule.Id)
return nil // rule grants the account access to the resource
case pb.Access_DENIED:
log.Tracef("%v denied access to %v:%v:%v by rule %v", logID, res.Type, res.Name, res.Endpoint, rule.Id)
return auth.ErrForbidden // rule denies access to the resource
}
}
}
// no rules were found for the resource, default to denying access
log.Tracef("%v denied access to %v:%v:%v by lack of rule", logID, res.Type, res.Name, res.Endpoint)
return auth.ErrForbidden
} }
// Inspect a token // Inspect a token
@ -221,35 +188,6 @@ func accessForRule(rule *pb.Rule, acc *auth.Account, res *auth.Resource) pb.Acce
return pb.Access_UNKNOWN return pb.Access_UNKNOWN
} }
// listRules gets all the rules from the store which match the filters.
// filters are namespace, type, name and then endpoint.
func (s *svc) listRules(filters ...string) []*pb.Rule {
s.Lock()
defer s.Unlock()
var rules []*pb.Rule
for _, r := range s.rules {
if len(filters) > 1 && r.Resource.Type != filters[0] {
continue
}
if len(filters) > 2 && r.Resource.Name != filters[1] {
continue
}
if len(filters) > 3 && r.Resource.Endpoint != filters[2] {
continue
}
rules = append(rules, r)
}
// sort rules by priority
sort.Slice(rules, func(i, j int) bool {
return rules[i].Priority < rules[j].Priority
})
return rules
}
// loadRules retrieves the rules from the auth service // loadRules retrieves the rules from the auth service
func (s *svc) loadRules() { func (s *svc) loadRules() {
rsp, err := s.rule.List(context.TODO(), &pb.ListRequest{}) rsp, err := s.rule.List(context.TODO(), &pb.ListRequest{})
@ -261,7 +199,27 @@ func (s *svc) loadRules() {
return return
} }
s.rules = rsp.Rules s.rules = make([]*auth.Rule, 0, len(rsp.Rules))
for _, r := range rsp.Rules {
var access auth.Access
if r.Access == pb.Access_GRANTED {
access = auth.AccessGranted
} else {
access = auth.AccessDenied
}
s.rules = append(s.rules, &auth.Rule{
ID: r.Id,
Role: r.Role,
Access: access,
Priority: r.Priority,
Resource: &auth.Resource{
Type: r.Resource.Type,
Name: r.Resource.Name,
Endpoint: r.Resource.Endpoint,
},
})
}
} }
func (s *svc) loadRulesIfEmpty() { func (s *svc) loadRulesIfEmpty() {

View File

@ -66,7 +66,7 @@ func Generate(id string, name string, a auth.Auth) error {
// generate the first token // generate the first token
tok, err := a.Token( tok, err := a.Token(
auth.WithCredentials(accID, accSecret), auth.WithToken(tok.RefreshToken),
auth.WithExpiry(time.Minute*10), auth.WithExpiry(time.Minute*10),
) )
if err != nil { if err != nil {