blob: f4d60c94d2f826abd00db195eff187f9e9f2dc44 [file] [log] [blame]
Asim Shankarda682852014-11-06 00:38:43 -08001package acl
2
3import (
4 "fmt"
5 "os"
6 "reflect"
7
8 "veyron.io/veyron/veyron2/security"
9)
10
11// TaggedACLAuthorizer implements an authorization policy where access is
12// granted if the remote end presents blessings included in the Access Control
13// Lists (ACLs) associated with the set of relevant tags.
14//
15// The set of relevant tags is the subset of tags associated with the
16// method (security.Context.MethodTags) that have the same type as tagType.
17// Currently, tagType.Kind must be reflect.String, i.e., only tags that are
18// named string types are supported.
19//
20// If multiple tags of tagType are associated with the method, then access is
21// granted if the peer presents blessings that match the ACLs of each one of
22// those tags. If no tags of tagType are associated with the method, then
23// access is denied.
24//
25// If the TaggedACLMap provided is nil, then a nil authorizer is returned.
26//
27// Sample usage:
28//
29// (1) Attach tags to methods in the VDL (eg. myservice.vdl)
30// package myservice
31//
32// type MyTag string
33// const (
34// ReadAccess = MyTag("R")
35// WriteAccess = MyTag("W")
36// )
37//
38// type MyService interface {
39// Get() ([]string, error) {ReadAccess}
40// GetIndex(int) (string, error) {ReadAccess}
41//
42// Set([]string) error {WriteAccess}
43// SetIndex(int, string) error {WriteAccess}
44//
45// GetAndSet([]string) ([]string, error) {ReadAccess, WriteAccess}
46// }
47//
48// (2) Setup the ipc.Dispatcher to use the TaggedACLAuthorizer
49// import (
50// "reflect"
51// "veyron.io/veyron/veyron/security/acl"
52//
53// "veyron.io/veyron/veyron2/ipc"
54// "veyron.io/veyron/veyron2/security"
55// )
56//
57// type dispatcher struct{}
58// func (d dispatcher) Lookup(suffix, method) (ipc.Invoker, security.Authorizer, error) {
59// acl := acl.TaggedACLMap{
60// "R": acl.ACL{In: []security.BlessingPattern{"alice/friends/...", "alice/family/..."} },
61// "W": acl.ACL{In: []security.BlessingPattern{"alice/family/...", "alice/colleagues/..." } },
62// }
63// typ := reflect.TypeOf(ReadAccess) // equivalently, reflect.TypeOf(WriteAccess)
64// return newInvoker(), acl.TaggedACLAuthorizer(acl, typ), nil
65// }
66//
67// With the above dispatcher, the server will grant access to a peer with the blessing
68// "alice/friend/bob" access only to the "Get" and "GetIndex" methods. A peer presenting
69// the blessing "alice/colleague/carol" will get access only to the "Set" and "SetIndex"
70// methods. A peer presenting "alice/family/mom" will get access to all methods, even
71// GetAndSet - which requires that the blessing appear in the ACLs for both the
72// ReadAccess and WriteAccess tags.
73func TaggedACLAuthorizer(acls TaggedACLMap, tagType reflect.Type) (security.Authorizer, error) {
74 if tagType.Kind() != reflect.String {
75 return nil, fmt.Errorf("tag type(%v) must be backed by a string not %v", tagType, tagType.Kind())
76 }
77 return &authorizer{acls, tagType}, nil
78}
79
80// TaggedACLAuthorizerFromFile applies the same authorization policy as
81// TaggedACLAuthorizer, with the TaggedACLMap to be used sourced from a file named
82// filename.
83//
84// Changes to the file are monitored and affect subsequent calls to Authorize.
85// Currently, this is achieved by re-reading the file on every call to
86// Authorize.
87// TODO(ashankar,ataly): Use inotify or a similar mechanism to watch for
88// changes.
89func TaggedACLAuthorizerFromFile(filename string, tagType reflect.Type) (security.Authorizer, error) {
90 if tagType.Kind() != reflect.String {
91 return nil, fmt.Errorf("tag type(%v) must be backed by a string not %v", tagType, tagType.Kind())
92 }
93 return &fileAuthorizer{filename, tagType}, nil
94}
95
96type authorizer struct {
97 acls TaggedACLMap
98 tagType reflect.Type
99}
100
101func (a *authorizer) Authorize(ctx security.Context) error {
102 // "Self-RPCs" are always authorized.
103 if l, r := ctx.LocalBlessings(), ctx.RemoteBlessings(); l != nil && r != nil && reflect.DeepEqual(l.PublicKey(), r.PublicKey()) {
104 return nil
105 }
106 var blessings []string
107 if ctx.RemoteBlessings() != nil {
108 blessings = ctx.RemoteBlessings().ForContext(ctx)
109 }
110 grant := false
111 for _, tag := range ctx.MethodTags() {
112 if v := reflect.ValueOf(tag); v.Type() == a.tagType {
113 if acl, exists := a.acls[v.String()]; !exists || !acl.Includes(blessings...) {
114 return errACLMatch(blessings)
115 }
116 grant = true
117 }
118 }
119 if grant {
120 return nil
121 }
122 return errACLMatch(blessings)
123}
124
125type fileAuthorizer struct {
126 filename string
127 tagType reflect.Type
128}
129
130func (a *fileAuthorizer) Authorize(ctx security.Context) error {
131 acl, err := loadTaggedACLMapFromFile(a.filename)
132 if err != nil {
133 // TODO(ashankar): Information leak?
134 return fmt.Errorf("failed to read ACL from file: %v", err)
135 }
136 return (&authorizer{acl, a.tagType}).Authorize(ctx)
137}
138
139func loadTaggedACLMapFromFile(filename string) (TaggedACLMap, error) {
140 file, err := os.Open(filename)
141 if err != nil {
142 return nil, err
143 }
144 defer file.Close()
145 return ReadTaggedACLMap(file)
146}
147
148func errACLMatch(blessings []string) error {
149 return fmt.Errorf("%v does not match ACL", blessings)
150}