Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
aos
GitHub Repository: aos/grafana-agent
Path: blob/main/component/remote/vault/vault.go
4096 views
1
package vault
2
3
import (
4
"context"
5
"fmt"
6
"sync"
7
"time"
8
9
"github.com/go-kit/log"
10
"github.com/go-kit/log/level"
11
"github.com/grafana/agent/component"
12
"github.com/grafana/agent/pkg/river/rivertypes"
13
"github.com/oklog/run"
14
15
vault "github.com/hashicorp/vault/api"
16
)
17
18
func init() {
19
component.Register(component.Registration{
20
Name: "remote.vault",
21
Args: Arguments{},
22
Exports: Exports{},
23
24
Build: func(opts component.Options, args component.Arguments) (component.Component, error) {
25
return New(opts, args.(Arguments))
26
},
27
})
28
}
29
30
// Arguments configures remote.vault.
31
type Arguments struct {
32
Server string `river:"server,attr"`
33
Namespace string `river:"namespace,attr,optional"`
34
35
Path string `river:"path,attr"`
36
37
RereadFrequency time.Duration `river:"reread_frequency,attr,optional"`
38
39
ClientOptions ClientOptions `river:"client_options,block,optional"`
40
41
// The user *must* provide exactly one Auth blocks. This must be a slice
42
// because the enum flag requires a slice and being tagged as optional.
43
//
44
// TODO(rfratto): allow the enum flag to be used with a non-slice type.
45
46
Auth []AuthArguments `river:"auth,enum,optional"`
47
}
48
49
// DefaultArguments holds default settings for Arguments.
50
var DefaultArguments = Arguments{
51
ClientOptions: ClientOptions{
52
MinRetryWait: 1000 * time.Millisecond,
53
MaxRetryWait: 1500 * time.Millisecond,
54
MaxRetries: 2,
55
Timeout: 60 * time.Second,
56
},
57
}
58
59
// client creates a Vault client from the arguments.
60
func (a *Arguments) client() (*vault.Client, error) {
61
cfg := vault.DefaultConfig()
62
cfg.Address = a.Server
63
cfg.MinRetryWait = a.ClientOptions.MinRetryWait
64
cfg.MaxRetryWait = a.ClientOptions.MaxRetryWait
65
cfg.MaxRetries = a.ClientOptions.MaxRetries
66
cfg.Timeout = a.ClientOptions.Timeout
67
68
return vault.NewClient(cfg)
69
}
70
71
// UnmarshalRiver implements river.Unmarshaler.
72
func (a *Arguments) UnmarshalRiver(f func(interface{}) error) error {
73
*a = DefaultArguments
74
75
type arguments Arguments
76
if err := f((*arguments)(a)); err != nil {
77
return err
78
}
79
80
if len(a.Auth) == 0 {
81
return fmt.Errorf("exactly one auth.* block must be specified; found none")
82
} else if len(a.Auth) > 1 {
83
return fmt.Errorf("exactly one auth.* block must be specified; found %d", len(a.Auth))
84
}
85
86
if a.ClientOptions.Timeout == 0 {
87
return fmt.Errorf("client_options.timeout must be greater than 0")
88
}
89
90
return nil
91
}
92
93
func (a *Arguments) authMethod() authMethod {
94
if len(a.Auth) != 1 {
95
panic(fmt.Sprintf("remote.vault: found %d auth types, expected 1", len(a.Auth)))
96
}
97
return a.Auth[0].authMethod()
98
}
99
100
func (a *Arguments) secretStore(cli *vault.Client) secretStore {
101
// TODO(rfratto): support different stores (like a logical store).
102
return &kvStore{c: cli}
103
}
104
105
// ClientOptions sets extra options on the Client.
106
type ClientOptions struct {
107
MinRetryWait time.Duration `river:"min_retry_wait,attr,optional"`
108
MaxRetryWait time.Duration `river:"max_retry_wait,attr,optional"`
109
MaxRetries int `river:"max_retries,attr,optional"`
110
Timeout time.Duration `river:"timeout,attr,optional"`
111
}
112
113
// Exports is the values exported by remote.vault.
114
type Exports struct {
115
// Data holds key-value pairs returned from Vault after retrieving the key.
116
// Any keys-value pairs returned from Vault which are not []byte or strings
117
// cannot be represented as secrets and are therefore ignored.
118
//
119
// However, it seems that most secrets engines don't actually return
120
// arbitrary data, so this limitation shouldn't cause any issues in practice.
121
Data map[string]rivertypes.Secret `river:"data,attr"`
122
}
123
124
// Component implements the remote.vault component.
125
type Component struct {
126
opts component.Options
127
log log.Logger
128
metrics *metrics
129
130
mut sync.RWMutex
131
args Arguments // Arguments to the component.
132
133
secretManager *tokenManager
134
authManager *tokenManager
135
}
136
137
var (
138
_ component.Component = (*Component)(nil)
139
_ component.HealthComponent = (*Component)(nil)
140
_ component.DebugComponent = (*Component)(nil)
141
)
142
143
// New creates a new remote.vault component. It will try to immediately read
144
// the secret from Vault and return an error if the secret can't be read or if
145
// authentication against the Vault server fails.
146
func New(opts component.Options, args Arguments) (*Component, error) {
147
c := &Component{
148
opts: opts,
149
log: opts.Logger,
150
metrics: newMetrics(opts.Registerer),
151
}
152
153
if err := c.Update(args); err != nil {
154
return nil, err
155
}
156
return c, nil
157
}
158
159
// Run runs the remote.vault component, managing the lifetime of the retrieved
160
// secret and renewing/rereading it as necessary.
161
func (c *Component) Run(ctx context.Context) error {
162
ctx, cancel := context.WithCancel(ctx)
163
defer cancel()
164
165
var rg run.Group
166
167
rg.Add(func() error {
168
c.secretManager.Run(ctx)
169
return nil
170
}, func(_ error) {
171
cancel()
172
})
173
174
rg.Add(func() error {
175
c.authManager.Run(ctx)
176
return nil
177
}, func(_ error) {
178
cancel()
179
})
180
181
return rg.Run()
182
}
183
184
// Update updates the remote.vault component. It will try to immediately read
185
// the secret from Vault and return an error if the secret can't be read.
186
func (c *Component) Update(args component.Arguments) error {
187
newArgs := args.(Arguments)
188
189
newClient, err := newArgs.client()
190
if err != nil {
191
return err
192
}
193
194
c.mut.Lock()
195
c.args = newArgs
196
c.mut.Unlock()
197
198
// Configure the token manager for authentication tokens and secrets.
199
// authManager *must* be configured first to ensure that the client is
200
// authenticated to Vault when retrieving the secret.
201
202
if c.authManager == nil {
203
// NOTE(rfratto): we pass 0 for the refresh interval because we don't
204
// support refreshing the auth token on an interval.
205
mgr, err := newTokenManager(tokenManagerOptions{
206
Log: log.With(c.log, "token_type", "auth"),
207
Client: newClient,
208
Getter: c.getAuthToken,
209
210
ReadCounter: c.metrics.authTotal,
211
RefreshCounter: c.metrics.authLeaseRenewalTotal,
212
})
213
if err != nil {
214
return err
215
}
216
c.authManager = mgr
217
} else {
218
c.authManager.SetClient(newClient)
219
}
220
221
if c.secretManager == nil {
222
mgr, err := newTokenManager(tokenManagerOptions{
223
Log: log.With(c.log, "token_type", "secret"),
224
Client: newClient,
225
Getter: c.getSecret,
226
RefreshInterval: newArgs.RereadFrequency,
227
228
ReadCounter: c.metrics.secretReadTotal,
229
RefreshCounter: c.metrics.secretLeaseRenewalTotal,
230
})
231
if err != nil {
232
return err
233
}
234
c.secretManager = mgr
235
} else {
236
c.secretManager.SetClient(newClient)
237
c.secretManager.SetRefreshInterval(newArgs.RereadFrequency)
238
}
239
240
return nil
241
}
242
243
func (c *Component) getAuthToken(ctx context.Context, cli *vault.Client) (*vault.Secret, error) {
244
c.mut.RLock()
245
defer c.mut.RUnlock()
246
247
authMethod := c.args.authMethod()
248
return authMethod.vaultAuthenticate(ctx, cli)
249
}
250
251
func (c *Component) getSecret(ctx context.Context, cli *vault.Client) (*vault.Secret, error) {
252
c.mut.RLock()
253
defer c.mut.RUnlock()
254
255
store := c.args.secretStore(cli)
256
secret, err := store.Read(ctx, &c.args)
257
if err != nil {
258
return nil, err
259
}
260
261
// Export the secret so other components can use it.
262
c.exportSecret(secret)
263
264
return secret, nil
265
}
266
267
// exportSecret converts the secret into exports and exports it to the
268
// controller.
269
func (c *Component) exportSecret(secret *vault.Secret) {
270
newExports := Exports{
271
Data: make(map[string]rivertypes.Secret),
272
}
273
274
for key, value := range secret.Data {
275
switch value := value.(type) {
276
case string:
277
newExports.Data[key] = rivertypes.Secret(value)
278
case []byte:
279
newExports.Data[key] = rivertypes.Secret(value)
280
281
default:
282
// Non-string secrets are ignored.
283
level.Warn(c.log).Log(
284
"msg", "found field in secret which cannot be converted into a string",
285
"key", key,
286
"type", fmt.Sprintf("%T", value),
287
)
288
}
289
}
290
291
c.opts.OnStateChange(newExports)
292
}
293
294
// CurrentHealth returns the current health of the remote.vault component. It
295
// will be healthy as long as the latest read or renewal was successful.
296
func (c *Component) CurrentHealth() component.Health {
297
return component.LeastHealthy(
298
c.authManager.CurrentHealth(),
299
c.secretManager.CurrentHealth(),
300
)
301
}
302
303
// DebugInfo returns debug information about the remote.vault component. It
304
// includes non-sensitive metadata about the current secret.
305
func (c *Component) DebugInfo() interface{} {
306
return debugInfo{
307
AuthToken: c.authManager.DebugInfo(),
308
Secret: c.secretManager.DebugInfo(),
309
}
310
}
311
312
type debugInfo struct {
313
AuthToken secretInfo `river:"auth_token,block"`
314
Secret secretInfo `river:"secret,block"`
315
}
316
317