diff --git a/acl/acl.go b/acl/acl.go index 8f2476616edd24f2f39825d27ef17f00071fc1fd..f8628d216179516a8ef6cd492e593aa653b48928 100644 --- a/acl/acl.go +++ b/acl/acl.go @@ -2,6 +2,7 @@ package acl import ( "bufio" + "errors" "fmt" "os" "strings" @@ -14,6 +15,7 @@ type ACLOp int const ( OpRead = iota OpWrite + OpPeer ) func (op ACLOp) String() string { @@ -22,6 +24,8 @@ func (op ACLOp) String() string { return "READ" case OpWrite: return "WRITE" + case OpPeer: + return "PEER" } return "<ERROR>" } @@ -61,6 +65,18 @@ func (m *Manager) Check(identity string, op ACLOp, path string) bool { return false } +// Load ACL rules from a text file. The file should contain one rule +// per line, either: +// +// <identity> <path> <op(READ|WRITE)> +// +// or: +// +// <identity> <op(PEER)> +// +// (PEER ACLs have no path). Empty lines and lines starting with a # +// are ignored. +// func Load(path string) (*Manager, error) { f, err := os.Open(path) if err != nil { @@ -77,19 +93,11 @@ func Load(path string) (*Manager, error) { if strings.HasPrefix(line, "#") || line == "" { continue } - fields := strings.Fields(line) - if len(fields) != 3 { - return nil, fmt.Errorf("syntax error at %s:%d: not enough fields", path, lineno) - } - op, err := parseOp(fields[2]) + e, err := parseEntry(line) if err != nil { return nil, fmt.Errorf("syntax error at %s:%d: %w", path, lineno, err) } - acls = append(acls, Entry{ - Identity: fields[0], - Path: fields[1], - Op: op, - }) + acls = append(acls, e) } if err := scanner.Err(); err != nil { @@ -99,12 +107,48 @@ func Load(path string) (*Manager, error) { return NewManager(acls), nil } +func parseEntry(line string) (e Entry, err error) { + fields := strings.Fields(line) + + switch len(fields) { + case 2: + e.Op, err = parseOp(fields[1]) + if err != nil { + return + } + if e.Op != OpPeer { + err = errors.New("non-PEER acl without path") + return + } + + case 3: + e.Path = fields[1] + e.Op, err = parseOp(fields[2]) + if err != nil { + return + } + if e.Op != OpRead && e.Op != OpWrite { + err = errors.New("acl op is not 'read' or 'write'") + return + } + + default: + err = errors.New("not enough fields") + return + } + + e.Identity = fields[0] + return +} + func parseOp(s string) (op ACLOp, err error) { switch strings.ToLower(s) { case "read", "r": op = OpRead case "write", "w": op = OpWrite + case "peer": + op = OpPeer default: err = fmt.Errorf("unknown op '%s'", s) } diff --git a/server.go b/server.go index 8eafdb756f0f49a4426a134a052efc016b9c57fc..5bc912d793248e23a04848005d040f55e5e4bfaf 100644 --- a/server.go +++ b/server.go @@ -101,6 +101,10 @@ func (s *Server) Store(ctx context.Context, req *pb.StoreRequest) (*empty.Empty, } func (s *Server) SyncNodes(ctx context.Context, req *pb.SyncNodesRequest) (*pb.SyncNodesResponse, error) { + if !s.acl.Check(common.GetAuthIdentity(ctx), acl.OpPeer, "") { + return nil, status.Error(codes.PermissionDenied, "unauthorized") + } + s.mx.Lock() diff := s.nodes.TreeDiff(req.Summary, "/") s.mx.Unlock()