--- a/access/access.go Fri May 03 13:03:43 2019 -0500
+++ b/access/access.go Wed May 08 02:14:09 2019 +0000
@@ -1,10 +1,10 @@
@@ -37,21 +37,23 @@
+ groups map[string][]string + permissions [numPerms][]string // Access represents a parsed access file.
- // pathsMu controls access to the patterns map
+ // keysMu controls access to the patterns map + // keys holds all users indexed by key fingerprint - // paths holds all keys that to a path, indexed by
- paths map[string][]_key
- global [numPerms][]_key
+ // usersMu controls access to the patterns map + // users holds its patterns permissions indexed + users map[string]map[string]Perm // parse calls inderectly UnmarshalYAML, it's caller's duty to control the access to a.
@@ -69,160 +71,127 @@
Write []string `yaml:"write"`
- Global acl `yaml:"global"`
- Groups map[string][]string `yaml:"groups"`
- Patterns map[string]acl `yaml:"patterns"`
+ Global acl `yaml:"global"` + Groups groups `yaml:"groups"` + Patterns map[string]acl `yaml:"patterns"` - groups := make(map[string][]_key)
- for name, v := range dummy.Groups {
- if isPublic([]byte(name)) {
- log.Errorf("access: %q group is reserved, ignored", name)
- if _, exists := groups[name]; exists {
- log.Errorf("access: group %q registered twice", name)
+ users := permissions{dummy.Global.Init, dummy.Global.Read, dummy.Global.Write} + globals := parseGlobals(dummy.Groups, users) + for pattern, v := range dummy.Patterns { + // validate the pattern + _, err := filepath.Match(pattern, "") + log.Errorf("malformed pattern %q: %v", pattern, err) - for _, keyFile := range v {
- k, err := loadKeys(keyFile)
- if _, ok := groups[name]; !ok {
- // if group "name" fail to load every key, this stops
- // setPath routines to load group names as keys
- keys := make([]_key, 0, len(k))
- keys = append(keys, _key{fprint: v})
- groups[name] = append(groups[name], keys...)
- gperms := [numPerms][]string{dummy.Global.Init, dummy.Global.Read, dummy.Global.Write}
- a.setGlobals(groups, gperms)
- for i, v := range dummy.Patterns {
- globs, err := filepath.Glob(filepath.Join(reposDir, i))
- log.Errorf("malformed pattern %q: %v", i, err)
- for _, path := range globs {
- perms := [numPerms][]string{v.Init, v.Read, v.Write}
- a.setPath(path, perms, groups)
+ perms := permissions{v.Init, v.Read, v.Write} + a.addPattern(pattern, perms, globals, dummy.Groups) -func (a *Access) addToPath(path string, p Perm, ks ..._key) {
- if keys, found := a.paths[path]; found {
- if n := k.find(keys); n >= 0 {
- a.paths[path][n].p.set(p)
+func (a *Access) addToUsers(pattern string, p Perm, users ...string) { + for _, user := range users { + if _, found := a.users[user]; found { + perm := a.users[user][pattern] + a.users[user][pattern] = perm
- a.paths[path] = append(a.paths[path], k)
+ // if user not seen, parse key and append to + if err := a.addToKeys(user); err != nil { + log.Errorf("addToUsers: %v", err) + a.users[user] = make(map[string]Perm) + a.users[user][pattern] = perm -func (a *Access) setPath(path string, perms [numPerms][]string, groups map[string][]_key) {
+func (a *Access) addPattern(pattern string, perms, globals permissions, g groups) { for p, yamlPerm := range perms {
// casting p is not required as of current Go tip (1.13)
// see: http://golang.org/issues/19113
perm := Perm(1 << uint32(p))
- a.addToPath(path, perm, a.global[0]...)
+ a.addToUsers(pattern, perm, globals[p]...) - for _, keyOrGroup := range yamlPerm {
- // if it's a group, just copy what we already loaded
- if _, ok := groups[keyOrGroup]; ok {
- // set permissions on groups key
- for i := range groups[keyOrGroup] {
- k := groups[keyOrGroup][i]
- a.addToPath(path, perm, k)
+ for _, userOrGroup := range yamlPerm { + if isPublic([]byte(userOrGroup)) { - // this is likely to change, it's here just to remember later.
- if keyOrGroup == "public" {
- a.addToPath(path, perm, a.global[p]...)
+ if _, ok := g[userOrGroup]; ok { + a.addToUsers(pattern, perm, g[userOrGroup]...) - ks, err := loadKeys(keyOrGroup)
- keys := make([]_key, 0, len(ks))
- keys = append(keys, _key{fprint: v})
- a.addToPath(path, perm, keys...)
+ a.addToUsers(pattern, perm, userOrGroup) -func (a *Access) setGlobals(groups map[string][]_key, perms [numPerms][]string) {
- for p, keyOrGroup := range perms {
+func (a *Access) addToKeys(user string) error { + ks, err := loadKeys(user) + return fmt.Errorf("addToKeys: %v", err) + for _, key := range ks { +func parseGlobals(g groups, users permissions) (globals permissions) { + for p, userOrGroup := range users { // casting p is not required as of current Go tip (1.13)
// see: http://golang.org/issues/19113
perm.set(Perm(1 << uint32(p)))
- for _, name := range keyOrGroup {
- // if it's a group, just copy what we already loaded
- if _, found := groups[name]; found {
- keys := make([]_key, 0, len(groups[name])+1)
- for _, v := range groups[name] {
- a.global[p] = append(a.global[p], keys...)
+ for _, name := range userOrGroup { + if isPublic([]byte(name)) { + if _, found := g[name]; found { + globals[p] = append(globals[p], g[name]...) - // this is likely to change, it's here just to remember later.
- a.global[p] = append(a.global[p], _key{fprint: "public", p: perm})
- k, err := loadKeys(name)
- keys := make([]_key, 0, len(k))
- keys = append(keys, _key{fprint: v, p: perm})
- a.global[p] = append(a.global[p], keys...)
+ globals[p] = append(globals[p], name) -// Can checks whenever if key has access to path
-func (a *Access) Can(path, key string, p Perm) bool {
- defer a.pathsMu.RUnlock()
- keys, found := a.paths[path]
+// Can reports whenever if key (fingerprint) is has access p to path +func (a *Access) Can(key, path string, p Perm) bool { + defer a.keysMu.RUnlock() + user, found := a.keys[key] - for _, v := range keys {
- if key == v.fprint && v.p.can(p) {
+ defer a.usersMu.RUnlock() + patterns := a.users[user] + for pattern, perm := range patterns { + // ignoring error because pattern was validated before + // its insertion in map + if ok, _ := filepath.Match(pattern, path); !ok { @@ -232,10 +201,16 @@
// Reset discards all access state and rebuild its state
func (a *Access) Reset() error {
- defer a.pathsMu.Unlock()
- for i := range a.paths {
+ defer a.keysMu.Unlock() + defer a.usersMu.Unlock() + for i := range a.users { + for i := range a.keys { f, err := os.Open(accessFile)
@@ -253,30 +228,24 @@
accessFile = filepath.Join(reposPath, "hgkeeper", AccessFile)
keysDir = filepath.Join(reposPath, "hgkeeper", KeysDir)
- a.paths = make(map[string][]_key)
+ a.users = make(map[string]map[string]Perm) + a.keys = make(map[string]string) // loadKeys reads file trying to parse it as an authorized key
// format, returning the finger prints of all keys found
func loadKeys(file string) ([]string, error) {
- f, err := os.Open(filepath.Join(keysDir, file))
+ f, err := ioutil.ReadFile(filepath.Join(keysDir, file)) return nil, fmt.Errorf("loadKeys %q: %v", file, err)
- keys := make([]string, 0, 20)
- // according to sshd(8) each line of the file contains one key,
- // ignoring empty and lines starting with #
- bio := bufio.NewScanner(f)
- for line := 1; bio.Scan(); line++ {
- if key == "" || key[0] == '#' {
- pub, _, _, _, err := ssh.ParseAuthorizedKey([]byte(key))
+ keys := make([]string, 0, 5) + pub, _, _, f, err = ssh.ParseAuthorizedKey(f) - log.Errorf("loadKeys %q:%d: %v", file, line, err)
+ log.Errorf("loadKeys %s: %v", file, err) keys = append(keys, ssh.FingerprintSHA256(pub))
@@ -295,26 +264,44 @@
// CheckPermission checks if we're supposed to allow the given ssh key. If the
// key is not found error is returned. If it is found, the username it belongs
-func CheckPermission(key ssh.PublicKey) (string, error) {
+func (a *Access) CheckPermission(key ssh.PublicKey) (string, error) { + defer a.keysMu.RUnlock() + fp := ssh.FingerprintSHA256(key) + return "", fmt.Errorf("access: check permission: key %q permission denied", fp) // GetPermissions will look up the given username and find the permissions that
-// the user has on the given path. It returns 3 bools for read, write, and
-func GetPermissions(username, path string) (bool, bool, bool) {
- return true, false, false
+// the user has on the given path. +func (a *Access) GetPermissions(username, path string) (read bool, write bool, init bool) { + defer a.usersMu.RUnlock() + patterns, ok := a.users[username] + for pattern, perm := range patterns { + // ignoring error because pattern was validated before + // its insertion in map + if ok, _ = filepath.Match(pattern, path); !ok { -// find looks for k in keys, if found returns the index in keys,
-// otherwise it returns -1
-func (k *_key) find(keys []_key) (n int) {
- if keys[n].fprint == k.fprint {
--- a/ssh/server.go Fri May 03 13:03:43 2019 -0500
+++ b/ssh/server.go Wed May 08 02:14:09 2019 +0000
@@ -67,7 +67,7 @@
func (s *Server) publicKeyCallback(meta ssh.ConnMetadata, key ssh.PublicKey) (*ssh.Permissions, error) {
- username, err := access.CheckPermission(key)
+ username, err := s.a.CheckPermission(key) @@ -121,7 +121,7 @@
username := serverConn.Permissions.Extensions["username"]
- r, _, _ := access.GetPermissions(username, cmd.path)
+ r, _, _ := s.a.GetPermissions(username, cmd.path) log.Warnf("user %q does not have read access to %s", username, cmd.path)