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.

195 lines
5.3 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"
"os"
)
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)
}
// 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
}
// 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),
}
}
// NewCredentialsStoreFromFile returns a new instance of a CredentialStore loaded from a file.
func NewCredentialsStoreFromFile(path string) (*CredentialsStore, error) {
f, err := os.Open(path)
if err != nil {
return nil, err
}
defer f.Close()
c := NewCredentialsStore()
return c, c.Load(f)
}
// 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]
return ok && pw == password
}
// 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
}
// Authenticate the user.
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()
return ok && c.HasPerm(username, perm)
}