Asim Shankar | da68285 | 2014-11-06 00:38:43 -0800 | [diff] [blame^] | 1 | package acl |
| 2 | |
| 3 | import ( |
| 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. |
| 73 | func 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. |
| 89 | func 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 | |
| 96 | type authorizer struct { |
| 97 | acls TaggedACLMap |
| 98 | tagType reflect.Type |
| 99 | } |
| 100 | |
| 101 | func (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 | |
| 125 | type fileAuthorizer struct { |
| 126 | filename string |
| 127 | tagType reflect.Type |
| 128 | } |
| 129 | |
| 130 | func (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 | |
| 139 | func 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 | |
| 148 | func errACLMatch(blessings []string) error { |
| 149 | return fmt.Errorf("%v does not match ACL", blessings) |
| 150 | } |