blob: f4d60c94d2f826abd00db195eff187f9e9f2dc44 [file] [log] [blame]
package acl
import (
"fmt"
"os"
"reflect"
"veyron.io/veyron/veyron2/security"
)
// TaggedACLAuthorizer implements an authorization policy where access is
// granted if the remote end presents blessings included in the Access Control
// Lists (ACLs) associated with the set of relevant tags.
//
// The set of relevant tags is the subset of tags associated with the
// method (security.Context.MethodTags) that have the same type as tagType.
// Currently, tagType.Kind must be reflect.String, i.e., only tags that are
// named string types are supported.
//
// If multiple tags of tagType are associated with the method, then access is
// granted if the peer presents blessings that match the ACLs of each one of
// those tags. If no tags of tagType are associated with the method, then
// access is denied.
//
// If the TaggedACLMap provided is nil, then a nil authorizer is returned.
//
// Sample usage:
//
// (1) Attach tags to methods in the VDL (eg. myservice.vdl)
// package myservice
//
// type MyTag string
// const (
// ReadAccess = MyTag("R")
// WriteAccess = MyTag("W")
// )
//
// type MyService interface {
// Get() ([]string, error) {ReadAccess}
// GetIndex(int) (string, error) {ReadAccess}
//
// Set([]string) error {WriteAccess}
// SetIndex(int, string) error {WriteAccess}
//
// GetAndSet([]string) ([]string, error) {ReadAccess, WriteAccess}
// }
//
// (2) Setup the ipc.Dispatcher to use the TaggedACLAuthorizer
// import (
// "reflect"
// "veyron.io/veyron/veyron/security/acl"
//
// "veyron.io/veyron/veyron2/ipc"
// "veyron.io/veyron/veyron2/security"
// )
//
// type dispatcher struct{}
// func (d dispatcher) Lookup(suffix, method) (ipc.Invoker, security.Authorizer, error) {
// acl := acl.TaggedACLMap{
// "R": acl.ACL{In: []security.BlessingPattern{"alice/friends/...", "alice/family/..."} },
// "W": acl.ACL{In: []security.BlessingPattern{"alice/family/...", "alice/colleagues/..." } },
// }
// typ := reflect.TypeOf(ReadAccess) // equivalently, reflect.TypeOf(WriteAccess)
// return newInvoker(), acl.TaggedACLAuthorizer(acl, typ), nil
// }
//
// With the above dispatcher, the server will grant access to a peer with the blessing
// "alice/friend/bob" access only to the "Get" and "GetIndex" methods. A peer presenting
// the blessing "alice/colleague/carol" will get access only to the "Set" and "SetIndex"
// methods. A peer presenting "alice/family/mom" will get access to all methods, even
// GetAndSet - which requires that the blessing appear in the ACLs for both the
// ReadAccess and WriteAccess tags.
func TaggedACLAuthorizer(acls TaggedACLMap, tagType reflect.Type) (security.Authorizer, error) {
if tagType.Kind() != reflect.String {
return nil, fmt.Errorf("tag type(%v) must be backed by a string not %v", tagType, tagType.Kind())
}
return &authorizer{acls, tagType}, nil
}
// TaggedACLAuthorizerFromFile applies the same authorization policy as
// TaggedACLAuthorizer, with the TaggedACLMap to be used sourced from a file named
// filename.
//
// Changes to the file are monitored and affect subsequent calls to Authorize.
// Currently, this is achieved by re-reading the file on every call to
// Authorize.
// TODO(ashankar,ataly): Use inotify or a similar mechanism to watch for
// changes.
func TaggedACLAuthorizerFromFile(filename string, tagType reflect.Type) (security.Authorizer, error) {
if tagType.Kind() != reflect.String {
return nil, fmt.Errorf("tag type(%v) must be backed by a string not %v", tagType, tagType.Kind())
}
return &fileAuthorizer{filename, tagType}, nil
}
type authorizer struct {
acls TaggedACLMap
tagType reflect.Type
}
func (a *authorizer) Authorize(ctx security.Context) error {
// "Self-RPCs" are always authorized.
if l, r := ctx.LocalBlessings(), ctx.RemoteBlessings(); l != nil && r != nil && reflect.DeepEqual(l.PublicKey(), r.PublicKey()) {
return nil
}
var blessings []string
if ctx.RemoteBlessings() != nil {
blessings = ctx.RemoteBlessings().ForContext(ctx)
}
grant := false
for _, tag := range ctx.MethodTags() {
if v := reflect.ValueOf(tag); v.Type() == a.tagType {
if acl, exists := a.acls[v.String()]; !exists || !acl.Includes(blessings...) {
return errACLMatch(blessings)
}
grant = true
}
}
if grant {
return nil
}
return errACLMatch(blessings)
}
type fileAuthorizer struct {
filename string
tagType reflect.Type
}
func (a *fileAuthorizer) Authorize(ctx security.Context) error {
acl, err := loadTaggedACLMapFromFile(a.filename)
if err != nil {
// TODO(ashankar): Information leak?
return fmt.Errorf("failed to read ACL from file: %v", err)
}
return (&authorizer{acl, a.tagType}).Authorize(ctx)
}
func loadTaggedACLMapFromFile(filename string) (TaggedACLMap, error) {
file, err := os.Open(filename)
if err != nil {
return nil, err
}
defer file.Close()
return ReadTaggedACLMap(file)
}
func errACLMatch(blessings []string) error {
return fmt.Errorf("%v does not match ACL", blessings)
}