// 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) }