mirror of https://github.com/status-im/consul.git
193 lines
5.3 KiB
Go
193 lines
5.3 KiB
Go
package token
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"io/ioutil"
|
|
"os"
|
|
"path/filepath"
|
|
|
|
"github.com/hashicorp/consul/lib/file"
|
|
)
|
|
|
|
// Logger used by Store.Load to report warnings.
|
|
type Logger interface {
|
|
Warn(msg string, args ...interface{})
|
|
}
|
|
|
|
// Config used by Store.Load, which includes tokens and settings for persistence.
|
|
type Config struct {
|
|
EnablePersistence bool
|
|
DataDir string
|
|
ACLDefaultToken string
|
|
ACLAgentToken string
|
|
ACLAgentMasterToken string
|
|
ACLReplicationToken string
|
|
|
|
EnterpriseConfig
|
|
}
|
|
|
|
const tokensPath = "acl-tokens.json"
|
|
|
|
// Load tokens from Config and optionally from a persisted file in the cfg.DataDir.
|
|
// If a token exists in both the persisted file and in the Config a warning will
|
|
// be logged and the persisted token will be used.
|
|
//
|
|
// Failures to load the persisted file will result in loading tokens from the
|
|
// config before returning the error.
|
|
func (t *Store) Load(cfg Config, logger Logger) error {
|
|
t.persistenceLock.RLock()
|
|
if !cfg.EnablePersistence {
|
|
t.persistence = nil
|
|
t.persistenceLock.RUnlock()
|
|
loadTokens(t, cfg, persistedTokens{}, logger)
|
|
return nil
|
|
}
|
|
|
|
defer t.persistenceLock.RUnlock()
|
|
t.persistence = &fileStore{
|
|
filename: filepath.Join(cfg.DataDir, tokensPath),
|
|
logger: logger,
|
|
}
|
|
return t.persistence.load(t, cfg)
|
|
}
|
|
|
|
// WithPersistenceLock executes f while hold a lock. If f returns a nil error,
|
|
// the tokens in Store will be persisted to the tokens file. Otherwise no
|
|
// tokens will be persisted, and the error from f will be returned.
|
|
//
|
|
// The lock is held so that the writes are persisted before some other thread
|
|
// can change the value.
|
|
func (t *Store) WithPersistenceLock(f func() error) error {
|
|
t.persistenceLock.Lock()
|
|
if t.persistence == nil {
|
|
t.persistenceLock.Unlock()
|
|
return f()
|
|
}
|
|
defer t.persistenceLock.Unlock()
|
|
return t.persistence.withPersistenceLock(t, f)
|
|
}
|
|
|
|
type persistedTokens struct {
|
|
Replication string `json:"replication,omitempty"`
|
|
AgentMaster string `json:"agent_master,omitempty"`
|
|
Default string `json:"default,omitempty"`
|
|
Agent string `json:"agent,omitempty"`
|
|
}
|
|
|
|
type fileStore struct {
|
|
filename string
|
|
logger Logger
|
|
}
|
|
|
|
func (p *fileStore) load(s *Store, cfg Config) error {
|
|
tokens, err := readPersistedFromFile(p.filename)
|
|
if err != nil {
|
|
p.logger.Warn("unable to load persisted tokens", "error", err)
|
|
}
|
|
loadTokens(s, cfg, tokens, p.logger)
|
|
return err
|
|
}
|
|
|
|
func loadTokens(s *Store, cfg Config, tokens persistedTokens, logger Logger) {
|
|
if tokens.Default != "" {
|
|
s.UpdateUserToken(tokens.Default, TokenSourceAPI)
|
|
|
|
if cfg.ACLDefaultToken != "" {
|
|
logger.Warn("\"default\" token present in both the configuration and persisted token store, using the persisted token")
|
|
}
|
|
} else {
|
|
s.UpdateUserToken(cfg.ACLDefaultToken, TokenSourceConfig)
|
|
}
|
|
|
|
if tokens.Agent != "" {
|
|
s.UpdateAgentToken(tokens.Agent, TokenSourceAPI)
|
|
|
|
if cfg.ACLAgentToken != "" {
|
|
logger.Warn("\"agent\" token present in both the configuration and persisted token store, using the persisted token")
|
|
}
|
|
} else {
|
|
s.UpdateAgentToken(cfg.ACLAgentToken, TokenSourceConfig)
|
|
}
|
|
|
|
if tokens.AgentMaster != "" {
|
|
s.UpdateAgentMasterToken(tokens.AgentMaster, TokenSourceAPI)
|
|
|
|
if cfg.ACLAgentMasterToken != "" {
|
|
logger.Warn("\"agent_master\" token present in both the configuration and persisted token store, using the persisted token")
|
|
}
|
|
} else {
|
|
s.UpdateAgentMasterToken(cfg.ACLAgentMasterToken, TokenSourceConfig)
|
|
}
|
|
|
|
if tokens.Replication != "" {
|
|
s.UpdateReplicationToken(tokens.Replication, TokenSourceAPI)
|
|
|
|
if cfg.ACLReplicationToken != "" {
|
|
logger.Warn("\"replication\" token present in both the configuration and persisted token store, using the persisted token")
|
|
}
|
|
} else {
|
|
s.UpdateReplicationToken(cfg.ACLReplicationToken, TokenSourceConfig)
|
|
}
|
|
|
|
loadEnterpriseTokens(s, cfg)
|
|
}
|
|
|
|
func readPersistedFromFile(filename string) (persistedTokens, error) {
|
|
tokens := persistedTokens{}
|
|
|
|
buf, err := ioutil.ReadFile(filename)
|
|
switch {
|
|
case os.IsNotExist(err):
|
|
// non-existence is not an error we care about
|
|
return tokens, nil
|
|
case err != nil:
|
|
return tokens, fmt.Errorf("failed reading tokens file %q: %w", filename, err)
|
|
}
|
|
|
|
if err := json.Unmarshal(buf, &tokens); err != nil {
|
|
return tokens, fmt.Errorf("failed to decode tokens file %q: %w", filename, err)
|
|
}
|
|
|
|
return tokens, nil
|
|
}
|
|
|
|
func (p *fileStore) withPersistenceLock(s *Store, f func() error) error {
|
|
if err := f(); err != nil {
|
|
return err
|
|
}
|
|
|
|
return p.saveToFile(s)
|
|
}
|
|
|
|
func (p *fileStore) saveToFile(s *Store) error {
|
|
tokens := persistedTokens{}
|
|
if tok, source := s.UserTokenAndSource(); tok != "" && source == TokenSourceAPI {
|
|
tokens.Default = tok
|
|
}
|
|
|
|
if tok, source := s.AgentTokenAndSource(); tok != "" && source == TokenSourceAPI {
|
|
tokens.Agent = tok
|
|
}
|
|
|
|
if tok, source := s.AgentMasterTokenAndSource(); tok != "" && source == TokenSourceAPI {
|
|
tokens.AgentMaster = tok
|
|
}
|
|
|
|
if tok, source := s.ReplicationTokenAndSource(); tok != "" && source == TokenSourceAPI {
|
|
tokens.Replication = tok
|
|
}
|
|
|
|
data, err := json.Marshal(tokens)
|
|
if err != nil {
|
|
p.logger.Warn("failed to persist tokens", "error", err)
|
|
return fmt.Errorf("Failed to marshal tokens for persistence: %v", err)
|
|
}
|
|
|
|
if err := file.WriteAtomicWithPerms(p.filename, data, 0700, 0600); err != nil {
|
|
p.logger.Warn("failed to persist tokens", "error", err)
|
|
return fmt.Errorf("Failed to persist tokens - %v", err)
|
|
}
|
|
return nil
|
|
}
|