access.go

  1package config
  2
  3import (
  4	"log"
  5	"strings"
  6
  7	"github.com/charmbracelet/soft-serve/proto"
  8	"github.com/gliderlabs/ssh"
  9	gossh "golang.org/x/crypto/ssh"
 10)
 11
 12var _ proto.Access = &Config{}
 13
 14// AuthRepo grants repo authorization to the given key.
 15func (c *Config) AuthRepo(repo string, pk ssh.PublicKey) proto.AccessLevel {
 16	return c.accessForKey(repo, pk)
 17}
 18
 19// User returns the user for the given public key. Can return nil if no user is
 20// found.
 21func (c *Config) User(pk ssh.PublicKey) (proto.User, error) {
 22	k := authorizedKey(pk)
 23	u, err := c.DB().GetUserByPublicKey(k)
 24	if err != nil {
 25		log.Printf("error getting user for key: %s", err)
 26		return nil, err
 27	}
 28	if u == nil {
 29		return nil, nil
 30	}
 31	return &user{
 32		cfg:  c,
 33		user: u,
 34	}, nil
 35}
 36
 37// IsCollab returns whether or not the given key is a collaborator on the given
 38// repository.
 39func (c *Config) IsCollab(repo string, pk ssh.PublicKey) bool {
 40	if c.isInitialAdminKey(pk) {
 41		return true
 42	}
 43
 44	isCollab, err := c.DB().IsRepoPublicKeyCollab(repo, authorizedKey(pk))
 45	if err != nil {
 46		log.Printf("error checking if key is repo collab: %v", err)
 47		return false
 48	}
 49	if isCollab {
 50		return true
 51	}
 52	return false
 53}
 54
 55// IsAdmin returns whether or not the given key is an admin.
 56func (c *Config) IsAdmin(pk ssh.PublicKey) bool {
 57	if c.isInitialAdminKey(pk) {
 58		return true
 59	}
 60
 61	u, err := c.User(pk)
 62	if err != nil {
 63		log.Printf("error getting user for key: %s", err)
 64		return false
 65	}
 66	return u.IsAdmin()
 67}
 68
 69// PasswordHandler returns whether or not password access is allowed.
 70func (c *Config) PasswordHandler(ctx ssh.Context, password string) bool {
 71	return (c.AnonAccess != proto.NoAccess) && c.SSH.AllowKeyless &&
 72		c.SSH.AllowPassword && (c.SSH.Password == password)
 73}
 74
 75// KeyboardInteractiveHandler returns whether or not keyboard interactive is allowed.
 76func (c *Config) KeyboardInteractiveHandler(ctx ssh.Context, _ gossh.KeyboardInteractiveChallenge) bool {
 77	return (c.AnonAccess != proto.NoAccess) && c.SSH.AllowKeyless
 78}
 79
 80// PublicKeyHandler returns whether or not the given public key may access the
 81// repo.
 82func (c *Config) PublicKeyHandler(ctx ssh.Context, pk ssh.PublicKey) bool {
 83	return c.accessForKey("", pk) != proto.NoAccess
 84}
 85
 86// accessForKey returns the access level for the given repo.
 87//
 88// If repo doesn't exist, then access is based on user's admin privileges, or
 89// config.AnonAccess.
 90// If repo exists, and private, then admins and collabs are allowed access.
 91// If repo exists, and not private, then access is based on config.AnonAccess.
 92func (c *Config) accessForKey(repo string, pk ssh.PublicKey) proto.AccessLevel {
 93	if c.isInitialAdminKey(pk) {
 94		return proto.AdminAccess
 95	}
 96
 97	var private bool
 98	anon := c.AnonAccess
 99	info, err := c.Metadata(repo)
100	if err == nil && info != nil {
101		private = info.IsPrivate()
102	}
103
104	log.Printf("auth key %s", authorizedKey(pk))
105	if pk != nil {
106		isAdmin := c.IsAdmin(pk)
107		if isAdmin {
108			return proto.AdminAccess
109		}
110		isCollab := c.IsCollab(repo, pk)
111		if isCollab {
112			if anon > proto.ReadWriteAccess {
113				return anon
114			}
115			return proto.ReadWriteAccess
116		}
117		if !private {
118			if anon > proto.ReadOnlyAccess {
119				return anon
120			}
121			return proto.ReadOnlyAccess
122		}
123	}
124	// Don't restrict access to private repos if no users are configured.
125	// Return anon access level.
126	if private {
127		return proto.NoAccess
128	}
129	return anon
130}
131
132func (c *Config) countUsers() int {
133	count, err := c.DB().CountUsers()
134	if err != nil {
135		return 0
136	}
137	return count
138}
139
140func (c *Config) isInitialAdminKey(key ssh.PublicKey) bool {
141	for _, k := range c.InitialAdminKeys {
142		pk, _, _, _, err := ssh.ParseAuthorizedKey([]byte(k))
143		if err != nil {
144			log.Printf("error parsing initial admin key: %v", err)
145			continue
146		}
147		if ssh.KeysEqual(key, pk) {
148			return true
149		}
150	}
151	return false
152}
153
154func authorizedKey(key ssh.PublicKey) string {
155	if key == nil {
156		return ""
157	}
158	return strings.TrimSpace(string(gossh.MarshalAuthorizedKey(key)))
159}