-
Notifications
You must be signed in to change notification settings - Fork 4.4k
/
persistence.go
192 lines (159 loc) · 5.31 KB
/
persistence.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
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
}