blob: fe9fca57776b7d0be59f7c7ca5b0df27b394ca6b [file] [log] [blame]
Tilak Sharma3ed30242014-08-11 11:45:55 -07001package security
2
3// This file provides an implementation of security.Authorizer.
4//
5// Definitions
6// * Self-RPC: An RPC request is said to be a "self-RPC" if the identities
7// at the local and remote ends are identical.
8
9import (
10 "errors"
11 "os"
12 "reflect"
13
Jiri Simsa519c5072014-09-17 21:37:57 -070014 "veyron.io/veyron/veyron2/security"
Tilak Sharma3ed30242014-08-11 11:45:55 -070015)
16
17var (
18 errACL = errors.New("no matching ACL entry found")
19 errInvalidLabel = errors.New("label is invalid")
20 errNilID = errors.New("identity being matched is nil")
Tilak Sharma3ed30242014-08-11 11:45:55 -070021)
22
23// aclAuthorizer implements Authorizer.
24type aclAuthorizer security.ACL
25
26// Authorize verifies a request iff the identity at the remote end has a name authorized by
27// the aclAuthorizer's ACL for the request's label, or the request corresponds to a self-RPC.
28func (a aclAuthorizer) Authorize(ctx security.Context) error {
29 // Test if the request corresponds to a self-RPC.
30 if ctx.LocalID() != nil && ctx.RemoteID() != nil && reflect.DeepEqual(ctx.LocalID(), ctx.RemoteID()) {
31 return nil
32 }
33 // Match the aclAuthorizer's ACL.
34 return matchesACL(ctx.RemoteID(), ctx.Label(), security.ACL(a))
35}
36
37// NewACLAuthorizer creates an authorizer from the provided ACL. The
38// authorizer authorizes a request iff the identity at the remote end has a name
39// authorized by the provided ACL for the request's label, or the request
40// corresponds to a self-RPC.
41func NewACLAuthorizer(acl security.ACL) security.Authorizer { return aclAuthorizer(acl) }
42
43// fileACLAuthorizer implements Authorizer.
44type fileACLAuthorizer string
45
46// Authorize reads and decodes the fileACLAuthorizer's ACL file into a ACL and
47// then verifies the request according to an aclAuthorizer based on the ACL. If
48// reading or decoding the file fails then no requests are authorized.
49func (a fileACLAuthorizer) Authorize(ctx security.Context) error {
50 acl, err := loadACLFromFile(string(a))
51 if err != nil {
52 return err
53 }
54 return aclAuthorizer(acl).Authorize(ctx)
55}
56
57// NewFileACLAuthorizer creates an authorizer from the provided path to a file
58// containing a JSON-encoded ACL. Each call to "Authorize" involves reading and
59// decoding a ACL from the file and then authorizing the request according to the
60// ACL. The authorizer monitors the file so out of band changes to the contents of
61// the file are reflected in the ACL. If reading or decoding the file fails then
62// no requests are authorized.
63//
64// The JSON-encoding of a ACL is essentially a JSON object describing a map from
Asim Shankar6bc64582014-08-27 12:51:42 -070065// BlessingPatterns to encoded LabelSets (see LabelSet.MarshalJSON).
Tilak Sharma3ed30242014-08-11 11:45:55 -070066// Examples:
Suharsh Sivakumar4c041db2014-09-04 13:19:05 -070067// * `{"..." : "RW"}` encodes an ACL that allows all principals to access all methods with
Tilak Sharma3ed30242014-08-11 11:45:55 -070068// ReadLabel or WriteLabel.
Suharsh Sivakumar4c041db2014-09-04 13:19:05 -070069// * `{"veyron/alice": "RW", "veyron/bob/...": "R"}` encodes an ACL that allows all principals
70// matched by "veyron/alice" to access methods with ReadLabel or WriteLabel, and all
71// principals matched by "veyron/bob/..." to access methods with ReadLabel.
72// (Also see BlessingPattern.MatchedBy)
Tilak Sharma3ed30242014-08-11 11:45:55 -070073//
74// TODO(ataly, ashankar): Instead of reading the file on each call we should use the "inotify"
75// mechanism to watch the file. Eventually we should also support ACLs stored in the Veyron
76// store.
77func NewFileACLAuthorizer(filePath string) security.Authorizer { return fileACLAuthorizer(filePath) }
78
79func matchesACL(id security.PublicID, label security.Label, acl security.ACL) error {
80 if id == nil {
81 return errNilID
82 }
Tilak Sharmad6ade0e2014-08-20 16:28:32 -070083 names := id.Names()
84 if len(names) == 0 {
85 // If id.Names() is empty, create a list of one empty name to force a
86 // call to CanAccess. Otherwise, ids with no names will not have access
87 // on an AllPrincipals ACL.
88 names = make([]string, 1)
89 }
90 for _, name := range names {
91 if acl.CanAccess(name, label) {
92 return nil
93 }
Tilak Sharma3ed30242014-08-11 11:45:55 -070094 }
95 return errACL
96}
97
98func loadACLFromFile(filePath string) (security.ACL, error) {
99 f, err := os.Open(filePath)
100 if err != nil {
Tilak Sharmab88a1112014-08-15 17:17:12 -0700101 return nullACL, err
Tilak Sharma3ed30242014-08-11 11:45:55 -0700102 }
103 defer f.Close()
Tilak Sharmad6ade0e2014-08-20 16:28:32 -0700104 return LoadACL(f)
Tilak Sharma3ed30242014-08-11 11:45:55 -0700105}