blob: 17609fdd3daec45d9235395d91a3dbcc3191ea84 [file] [log] [blame]
// Copyright 2015 The Vanadium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package access
import (
"os"
"v.io/v23/context"
"v.io/v23/security"
"v.io/v23/vdl"
"v.io/v23/verror"
)
const pkgPath = "v.io/v23/security/access"
var (
errTagNeedsString = verror.Register(pkgPath+".errTagNeedsString", verror.NoRetry, "{1:}{2:}tag type({3}) must be backed by a string not {4}{:_}")
errNoMethodTags = verror.Register(pkgPath+".errNoMethodTags", verror.NoRetry, "{1:}{2:}PermissionsAuthorizer.Authorize called on {3}.{4}, which has no tags of type {5}; this is likely unintentional{:_}")
errMultipleMethodTags = verror.Register(pkgPath+".errMultipleMethodTags", verror.NoRetry, "{1:}{2:}PermissionsAuthorizer on {3}.{4} cannot handle multiple tags of type {5} ({6}); this is likely unintentional{:_}")
errCantReadPermissionsFromFile = verror.Register(pkgPath+".errCantReadPermissionsFromFile", verror.NoRetry, "{1:}{2:}failed to read Permissions from file{:_}")
)
// PermissionsAuthorizer implements an authorization policy where access is
// granted if the remote end presents blessings included in the Access Control
// Lists (AccessLists) associated with the set of relevant tags.
//
// The set of relevant tags is the subset of tags associated with the
// method (security.Call.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.
//
// PermissionsAuthorizer expects exactly one tag of tagType to be associated
// with the method. If there are multiple, it fails authorization and returns
// an error. However, if multiple tags become a common occurrence, then this
// behavior may change.
//
// If the Permissions 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}
// }
//
// (2) Configure the rpc.Dispatcher to use the PermissionsAuthorizer
// import (
// "reflect"
//
// "v.io/v23/rpc"
// "v.io/v23/security"
// "v.io/v23/security/access"
// )
//
// type dispatcher struct{}
// func (d dispatcher) Lookup(suffix, method) (rpc.Invoker, security.Authorizer, error) {
// perms := access.Permissions{
// "R": access.AccessList{In: []security.BlessingPattern{"alice:friends", "alice:family"} },
// "W": access.AccessList{In: []security.BlessingPattern{"alice:family", "alice:colleagues" } },
// }
// typ := reflect.TypeOf(ReadAccess) // equivalently, reflect.TypeOf(WriteAccess)
// return newInvoker(), access.PermissionsAuthorizer(perms, 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.
func PermissionsAuthorizer(perms Permissions, tagType *vdl.Type) (security.Authorizer, error) {
if tagType.Kind() != vdl.String {
return nil, errTagType(tagType)
}
return &authorizer{perms, tagType}, nil
}
// TypicalTagTypePermissionsAuthorizer is like PermissionsAuthorizer, but
// assumes TypicalTagType and thus avoids returning an error.
func TypicalTagTypePermissionsAuthorizer(perms Permissions) security.Authorizer {
return &authorizer{perms, TypicalTagType()}
}
// PermissionsAuthorizerFromFile applies the same authorization policy as
// PermissionsAuthorizer, with the Permissions 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 PermissionsAuthorizerFromFile(filename string, tagType *vdl.Type) (security.Authorizer, error) {
if tagType.Kind() != vdl.String {
return nil, errTagType(tagType)
}
return &fileAuthorizer{filename, tagType}, nil
}
func errTagType(tt *vdl.Type) error {
return verror.New(errTagNeedsString, nil, verror.New(errTagNeedsString, nil, tt, tt.Kind()))
}
type authorizer struct {
perms Permissions
tagType *vdl.Type
}
func (a *authorizer) Authorize(ctx *context.T, call security.Call) error {
blessings, invalid := security.RemoteBlessingNames(ctx, call)
hastag := false
for _, tag := range call.MethodTags() {
if tag.Type() == a.tagType {
if hastag {
return verror.New(errMultipleMethodTags, ctx, call.Suffix(), call.Method(), a.tagType, call.MethodTags())
}
hastag = true
if acl, exists := a.perms[tag.RawString()]; !exists || !acl.Includes(blessings...) {
return NewErrNoPermissions(ctx, blessings, invalid, tag.RawString())
}
}
}
if !hastag {
return verror.New(errNoMethodTags, ctx, call.Suffix(), call.Method(), a.tagType)
}
return nil
}
type fileAuthorizer struct {
filename string
tagType *vdl.Type
}
func (a *fileAuthorizer) Authorize(ctx *context.T, call security.Call) error {
perms, err := loadPermissionsFromFile(a.filename)
if err != nil {
// TODO(ashankar): Information leak?
return verror.New(errCantReadPermissionsFromFile, ctx, err)
}
return (&authorizer{perms, a.tagType}).Authorize(ctx, call)
}
func loadPermissionsFromFile(filename string) (Permissions, error) {
file, err := os.Open(filename)
if err != nil {
return nil, err
}
defer file.Close()
return ReadPermissions(file)
}