1
0
Fork 0
You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

254 lines
6.6 KiB
Go

// Package auth is a lightweight credential store.
// It provides functionality for loading credentials, as well as validating credentials.
package auth
import (
"encoding/json"
"io"
"sync"
"golang.org/x/crypto/bcrypt"
)
const (
// AllUsers is the username that indicates all users, even anonymous users (requests without
// any BasicAuth information).
AllUsers = "*"
// PermAll means all actions permitted.
PermAll = "all"
// PermJoin means user is permitted to join cluster.
PermJoin = "join"
// PermJoinReadOnly means user is permitted to join the cluster only as a read-only node
PermJoinReadOnly = "join-read-only"
// PermRemove means user is permitted to remove a node.
PermRemove = "remove"
// PermExecute means user can access execute endpoint.
PermExecute = "execute"
// PermQuery means user can access query endpoint
PermQuery = "query"
// PermStatus means user can retrieve node status.
PermStatus = "status"
// PermReady means user can retrieve ready status.
PermReady = "ready"
// PermBackup means user can backup node.
PermBackup = "backup"
// PermLoad means user can load a SQLite dump into a node.
PermLoad = "load"
)
// BasicAuther is the interface an object must support to return basic auth information.
type BasicAuther interface {
BasicAuth() (string, string, bool)
}
// HashCache store hash values for users. Safe for use from multiple goroutines.
type HashCache struct {
mu sync.RWMutex
m map[string]map[string]bool
}
// NewHashCache returns a instantiated HashCache
func NewHashCache() *HashCache {
return &HashCache{
m: make(map[string]map[string]bool),
}
}
// Check returns whether hash is valid for username.
func (h *HashCache) Check(username, hash string) bool {
h.mu.RLock()
defer h.mu.RUnlock()
m, ok := h.m[username]
if !ok {
return false
}
_, ok = m[hash]
return ok
}
// Store stores the given hash as a valid hash for username.
func (h *HashCache) Store(username, hash string) {
h.mu.Lock()
defer h.mu.Unlock()
_, ok := h.m[username]
if !ok {
h.m[username] = make(map[string]bool)
}
h.m[username][hash] = true
}
// Credential represents authentication and authorization configuration for a single user.
type Credential struct {
Username string `json:"username,omitempty"`
Password string `json:"password,omitempty"`
Perms []string `json:"perms,omitempty"`
}
// CredentialsStore stores authentication and authorization information for all users.
type CredentialsStore struct {
store map[string]string
perms map[string]map[string]bool
UseCache bool
hashCache *HashCache
}
// NewCredentialsStore returns a new instance of a CredentialStore.
func NewCredentialsStore() *CredentialsStore {
return &CredentialsStore{
store: make(map[string]string),
perms: make(map[string]map[string]bool),
hashCache: NewHashCache(),
UseCache: true,
}
}
// Load loads credential information from a reader.
func (c *CredentialsStore) Load(r io.Reader) error {
dec := json.NewDecoder(r)
// Read open bracket
_, err := dec.Token()
if err != nil {
return err
}
var cred Credential
for dec.More() {
err := dec.Decode(&cred)
if err != nil {
return err
}
c.store[cred.Username] = cred.Password
c.perms[cred.Username] = make(map[string]bool, len(cred.Perms))
for _, p := range cred.Perms {
c.perms[cred.Username][p] = true
}
}
// Read closing bracket.
_, err = dec.Token()
if err != nil {
return err
}
return nil
}
// Check returns true if the password is correct for the given username.
func (c *CredentialsStore) Check(username, password string) bool {
pw, ok := c.store[username]
if !ok {
return false
}
// Simple match with plaintext password in creds?
if password == pw {
return true
}
// Maybe the given password is a hash -- check if the hash is good
// for the given user. We use a cache to avoid recomputing a value we
// previously computed (at substantial compute cost).
if c.UseCache && c.hashCache.Check(username, password) {
return true
}
// Next, what's in the file may be hashed, so hash the given password
// and compare.
if bcrypt.CompareHashAndPassword([]byte(pw), []byte(password)) != nil {
return false
}
// It's good -- cache that result for this user.
c.hashCache.Store(username, password)
return true
}
// Password returns the password for the given user.
func (c *CredentialsStore) Password(username string) (string, bool) {
pw, ok := c.store[username]
return pw, ok
}
// CheckRequest returns true if b contains a valid username and password.
func (c *CredentialsStore) CheckRequest(b BasicAuther) bool {
username, password, ok := b.BasicAuth()
if !ok || !c.Check(username, password) {
return false
}
return true
}
// HasPerm returns true if username has the given perm, either directly or
// via AllUsers. It does not perform any password checking.
func (c *CredentialsStore) HasPerm(username string, perm string) bool {
if m, ok := c.perms[username]; ok {
if _, ok := m[perm]; ok {
return true
}
}
if m, ok := c.perms[AllUsers]; ok {
if _, ok := m[perm]; ok {
return true
}
}
return false
}
// HasAnyPerm returns true if username has at least one of the given perms,
// either directly, or via AllUsers. It does not perform any password checking.
func (c *CredentialsStore) HasAnyPerm(username string, perm ...string) bool {
return func(p []string) bool {
for i := range p {
if c.HasPerm(username, p[i]) {
return true
}
}
return false
}(perm)
}
// AA authenticates and checks authorization for the given username and password
// for the given perm. If the credential store is nil, then this function always
// returns true. If AllUsers have the given perm, authentication is not done.
// Only then are the credentials checked, and then the perm checked.
func (c *CredentialsStore) AA(username, password, perm string) bool {
// No credential store? Auth is not even enabled.
if c == nil {
return true
}
// Is the required perm granted to all users, including anonymous users?
if c.HasAnyPerm(AllUsers, perm, PermAll) {
return true
}
// At this point a username needs to have been supplied
if username == "" {
return false
}
// Are the creds good?
if !c.Check(username, password) {
return false
}
// Is the specified user authorized?
return c.HasAnyPerm(username, perm, PermAll)
}
// HasPermRequest returns true if the username returned by b has the givem perm.
// It does not perform any password checking, but if there is no username
// in the request, it returns false.
func (c *CredentialsStore) HasPermRequest(b BasicAuther, perm string) bool {
username, _, ok := b.BasicAuth()
if !ok {
return false
}
return c.HasPerm(username, perm)
}