Skip to content

Commit 5a78dae

Browse files
memd0ugal
andauthored
fix: Change how the secret store is handled (#1281)
* fix: move handling of secrets store around In the current version of the code, the secret store is populated when the prober is created. The problem with that is that the prober is created *once* in the lifetime of the check, but the secret store needs to be updated more or less on a continuous basis (because it contains the token used to access the secrets). I'm sure there's a better way to handle this, but it took me a while to figure out the issue. Signed-off-by: Marcelo E. Magallon <[email protected]> Co-authored-by: Dougal Matthews <[email protected]>
1 parent 371fe13 commit 5a78dae

File tree

18 files changed

+271
-120
lines changed

18 files changed

+271
-120
lines changed

cmd/synthetic-monitoring-agent/main.go

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,19 @@ import (
4343
const (
4444
exitFail = 1
4545
defTelemetryTimeSpan = 5 // min
46+
47+
// The expiration time for access tokens is 10 minutes. Until we get
48+
// expiration information from the API, set this to a low number.
49+
// Normally it should be 15 minutes.
50+
//
51+
// This specific number is accounting for ~ 3 minutes of total
52+
// execution time for scripts, plus 30 seconds of buffer for retrieving
53+
// secrets, sending script to runner, etc. We expect secrets to be
54+
// loaded near the start of the script, but given it's a program we
55+
// cannot enforce that. k6 would have enforced that by making secrets
56+
// part of the options block, but we do not want to go anywhere near
57+
// that.
58+
secretsTimeout = 450 * time.Second // 7.5 minutes
4659
)
4760

4861
// run is the main entry point for the program.
@@ -277,7 +290,13 @@ func run(args []string, stdout io.Writer) error {
277290
})
278291
}
279292

280-
tm := tenants.NewManager(ctx, synthetic_monitoring.NewTenantsClient(conn), tenantCh, 15*time.Minute)
293+
tm := tenants.NewManager(
294+
ctx,
295+
synthetic_monitoring.NewTenantsClient(conn),
296+
tenantCh,
297+
secretsTimeout,
298+
zl.With().Str("subsystem", "tenant_manager").Logger(),
299+
)
281300

282301
pusherRegistry := pusher.NewRegistry[pusher.Factory]()
283302
pusherRegistry.MustRegister(pusherV1.Name, pusherV1.NewPublisher)

internal/adhoc/adhoc_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -600,6 +600,6 @@ func (r *testK6Runner) WithLogger(logger *zerolog.Logger) k6runner.Runner {
600600
return r
601601
}
602602

603-
func (r *testK6Runner) Run(ctx context.Context, script k6runner.Script) (*k6runner.RunResponse, error) {
603+
func (r *testK6Runner) Run(ctx context.Context, script k6runner.Script, secretStore k6runner.SecretStore) (*k6runner.RunResponse, error) {
604604
return &k6runner.RunResponse{}, nil
605605
}

internal/checks/checks_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -504,7 +504,7 @@ func (noopRunner) WithLogger(logger *zerolog.Logger) k6runner.Runner {
504504
return r
505505
}
506506

507-
func (noopRunner) Run(ctx context.Context, script k6runner.Script) (*k6runner.RunResponse, error) {
507+
func (noopRunner) Run(ctx context.Context, script k6runner.Script, secretStore k6runner.SecretStore) (*k6runner.RunResponse, error) {
508508
return &k6runner.RunResponse{}, nil
509509
}
510510

internal/k6runner/http.go

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -44,8 +44,9 @@ func (r requestError) Error() string {
4444

4545
// HTTPRunRequest
4646
type HTTPRunRequest struct {
47-
Script `json:",inline"`
48-
NotAfter time.Time `json:"notAfter"`
47+
Script `json:",inline"`
48+
SecretStore SecretStore `json:",inline"`
49+
NotAfter time.Time `json:"notAfter"`
4950
}
5051

5152
type RunResponse struct {
@@ -63,7 +64,7 @@ func (r HttpRunner) WithLogger(logger *zerolog.Logger) Runner {
6364

6465
var ErrUnexpectedStatus = errors.New("unexpected status code")
6566

66-
func (r HttpRunner) Run(ctx context.Context, script Script) (*RunResponse, error) {
67+
func (r HttpRunner) Run(ctx context.Context, script Script, secretStore SecretStore) (*RunResponse, error) {
6768
if r.backoff == 0 {
6869
panic("zero backoff, runner is misconfigured, refusing to DoS")
6970
}
@@ -92,7 +93,7 @@ func (r HttpRunner) Run(ctx context.Context, script Script) (*RunResponse, error
9293
start := time.Now()
9394

9495
var err error
95-
response, err = r.request(ctx, script)
96+
response, err = r.request(ctx, script, secretStore)
9697
if err == nil {
9798
r.logger.Debug().Bytes("metrics", response.Metrics).Bytes("logs", response.Logs).Msg("script result")
9899
r.metrics.Requests.With(map[string]string{metricLabelSuccess: "1", metricLabelRetriable: ""}).Inc()
@@ -136,7 +137,7 @@ func (r HttpRunner) Run(ctx context.Context, script Script) (*RunResponse, error
136137
// errRetryable indicates that an error is retryable. It is typically joined with another error.
137138
var errRetryable = errors.New("retryable")
138139

139-
func (r HttpRunner) request(ctx context.Context, script Script) (*RunResponse, error) {
140+
func (r HttpRunner) request(ctx context.Context, script Script, secretStore SecretStore) (*RunResponse, error) {
140141
checkTimeout := time.Duration(script.Settings.Timeout) * time.Millisecond
141142
if checkTimeout == 0 {
142143
return nil, ErrNoTimeout
@@ -161,8 +162,9 @@ func (r HttpRunner) request(ctx context.Context, script Script) (*RunResponse, e
161162
// This allows runners to not waste time running scripts which will not complete before the client gives up on the
162163
// request.
163164
runRequest := HTTPRunRequest{
164-
Script: script,
165-
NotAfter: notAfter,
165+
Script: script,
166+
SecretStore: secretStore,
167+
NotAfter: notAfter,
166168
}
167169

168170
reqBody, err := json.Marshal(runRequest)

internal/k6runner/http_test.go

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ func TestHttpRunnerRun(t *testing.T) {
8080
ctx, cancel := testhelper.Context(ctx, t)
8181
t.Cleanup(cancel)
8282

83-
_, err := runner.Run(ctx, script)
83+
_, err := runner.Run(ctx, script, SecretStore{})
8484
require.NoError(t, err)
8585
}
8686

@@ -129,7 +129,7 @@ func TestHttpRunnerRunError(t *testing.T) {
129129
ctx, cancel = testhelper.Context(ctx, t)
130130
t.Cleanup(cancel)
131131

132-
_, err := runner.Run(ctx, script)
132+
_, err := runner.Run(ctx, script, SecretStore{})
133133
require.ErrorIs(t, err, ErrUnexpectedStatus)
134134
}
135135

@@ -322,7 +322,7 @@ func TestScriptHTTPRun(t *testing.T) {
322322
zlogger = zerolog.Nop()
323323
)
324324

325-
success, err := script.Run(ctx, registry, logger, zlogger)
325+
success, err := script.Run(ctx, registry, logger, zlogger, SecretStore{})
326326
require.Equal(t, tc.expectSuccess, success)
327327
require.Equal(t, tc.expectLogs, logger.buf.String())
328328
if tc.expectErrorAs == nil {
@@ -457,7 +457,7 @@ func TestHTTPProcessorRetries(t *testing.T) {
457457
logger testLogger
458458
zlogger = zerolog.New(io.Discard)
459459
)
460-
success, err := processor.Run(ctx, registry, &logger, zlogger)
460+
success, err := processor.Run(ctx, registry, &logger, zlogger, SecretStore{})
461461
require.ErrorIs(t, err, tc.expectError)
462462
require.Equal(t, tc.expectError == nil, success)
463463
require.Equal(t, tc.expectRequests, requests.Load())
@@ -503,7 +503,7 @@ func TestHTTPProcessorRetries(t *testing.T) {
503503
logger testLogger
504504
zlogger = zerolog.New(io.Discard)
505505
)
506-
success, err := processor.Run(ctx, registry, &logger, zlogger)
506+
success, err := processor.Run(ctx, registry, &logger, zlogger, SecretStore{})
507507
require.NoError(t, err)
508508
require.True(t, success)
509509
})

internal/k6runner/k6runner.go

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,6 @@ type Script struct {
2929
// CheckInfo holds information about the SM check that triggered this script.
3030
CheckInfo CheckInfo `json:"check"`
3131
// SecretStore holds the location and token for accessing secrets
32-
SecretStore SecretStore `json:"secretStore"`
33-
// TODO: Add features.
3432
}
3533

3634
type SecretStore struct {
@@ -92,7 +90,7 @@ var ErrNoTimeout = errors.New("check has no timeout")
9290

9391
type Runner interface {
9492
WithLogger(logger *zerolog.Logger) Runner
95-
Run(ctx context.Context, script Script) (*RunResponse, error)
93+
Run(ctx context.Context, script Script, secretStore SecretStore) (*RunResponse, error)
9694
}
9795

9896
type RunnerOpts struct {
@@ -149,11 +147,11 @@ var (
149147
ErrFromRunner = errors.New("runner reported an error")
150148
)
151149

152-
func (r Processor) Run(ctx context.Context, registry *prometheus.Registry, logger logger.Logger, internalLogger zerolog.Logger) (bool, error) {
150+
func (r Processor) Run(ctx context.Context, registry *prometheus.Registry, logger logger.Logger, internalLogger zerolog.Logger, secretStore SecretStore) (bool, error) {
153151
k6runner := r.runner.WithLogger(&internalLogger)
154152

155153
// TODO: This error message is okay to be Debug for local k6 execution, but should be Error for remote runners.
156-
result, err := k6runner.Run(ctx, r.script)
154+
result, err := k6runner.Run(ctx, r.script, secretStore)
157155
if err != nil {
158156
internalLogger.Debug().
159157
Err(err).

internal/k6runner/k6runner_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ func TestScriptRun(t *testing.T) {
9090
// We already know tha parsing the metrics and the logs is working, so
9191
// we are only interested in verifying that the script runs without
9292
// errors.
93-
success, err := processor.Run(ctx, registry, &logger, zlogger)
93+
success, err := processor.Run(ctx, registry, &logger, zlogger, SecretStore{})
9494
require.NoError(t, err)
9595
require.True(t, success)
9696
}
@@ -130,7 +130,7 @@ type testRunner struct {
130130

131131
var _ Runner = &testRunner{}
132132

133-
func (r *testRunner) Run(ctx context.Context, script Script) (*RunResponse, error) {
133+
func (r *testRunner) Run(ctx context.Context, script Script, secretStore SecretStore) (*RunResponse, error) {
134134
return &RunResponse{
135135
Metrics: r.metrics,
136136
Logs: r.logs,

internal/k6runner/local.go

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ func (r Local) WithLogger(logger *zerolog.Logger) Runner {
3535
return r
3636
}
3737

38-
func (r Local) Run(ctx context.Context, script Script) (*RunResponse, error) {
38+
func (r Local) Run(ctx context.Context, script Script, secretStore SecretStore) (*RunResponse, error) {
3939
logger := r.logger.With().Object("checkInfo", &script.CheckInfo).Logger()
4040

4141
afs := afero.Afero{Fs: r.fs}
@@ -78,7 +78,7 @@ func (r Local) Run(ctx context.Context, script Script) (*RunResponse, error) {
7878
executable := r.k6path
7979
// TODO(d0ugal): This is a short-term hack to use a different k6 binary when
8080
// secrets are configured. This should be removed when the default binary has been updated
81-
if script.SecretStore.IsConfigured() {
81+
if secretStore.IsConfigured() {
8282
executable = r.k6path + "-gsm"
8383
logger.Info().
8484
Str("k6_path", r.k6path).
@@ -96,13 +96,18 @@ func (r Local) Run(ctx context.Context, script Script) (*RunResponse, error) {
9696
defer cancel()
9797

9898
var configFile string
99-
if script.SecretStore.IsConfigured() {
99+
if secretStore.IsConfigured() {
100100
var cleanup func()
101-
configFile, cleanup, err = createSecretConfigFile(script.SecretStore.Url, script.SecretStore.Token)
101+
configFile, cleanup, err = createSecretConfigFile(secretStore.Url, secretStore.Token)
102102
if err != nil {
103103
return nil, fmt.Errorf("cannot create secret config file: %w", err)
104104
}
105105
defer cleanup()
106+
107+
logger.Debug().
108+
Str("secret_config_file", configFile).
109+
Str("secrets_url", secretStore.Url).
110+
Msg("Using secret config file")
106111
}
107112

108113
args, err := r.buildK6Args(script, metricsFn, logsFn, scriptFn, configFile)
@@ -213,7 +218,7 @@ func (r Local) buildK6Args(script Script, metricsFn, logsFn, scriptFn, configFil
213218
}
214219

215220
// Add secretStore configuration if available
216-
if script.SecretStore.IsConfigured() {
221+
if configFile != "" {
217222
args = append(args, "--secret-source", "grafanasecrets=config="+configFile)
218223
}
219224

internal/k6runner/local_test.go

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -110,12 +110,7 @@ func TestBuildK6Args(t *testing.T) {
110110
},
111111
},
112112
"script with secrets": {
113-
script: Script{
114-
SecretStore: SecretStore{
115-
Url: secretUrl,
116-
Token: "secret-token",
117-
},
118-
},
113+
script: Script{},
119114
metricsFn: "/tmp/metrics.json",
120115
logsFn: "/tmp/logs.log",
121116
scriptFn: "/tmp/script.js",

internal/prober/browser/browser.go

Lines changed: 34 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,10 @@ type Module struct {
2424
}
2525

2626
type Prober struct {
27-
logger zerolog.Logger
28-
module Module
29-
processor *k6runner.Processor
27+
logger zerolog.Logger
28+
module Module
29+
processor *k6runner.Processor
30+
secretsRetriever func(context.Context) (k6runner.SecretStore, error)
3031
}
3132

3233
func NewProber(ctx context.Context, check model.Check, logger zerolog.Logger, runner k6runner.Runner, store secrets.SecretProvider) (Prober, error) {
@@ -36,11 +37,6 @@ func NewProber(ctx context.Context, check model.Check, logger zerolog.Logger, ru
3637
return p, errUnsupportedCheck
3738
}
3839

39-
secretStore, err := store.GetSecretCredentials(ctx, check.GlobalTenantID())
40-
if err != nil {
41-
return p, err
42-
}
43-
4440
p.module = Module{
4541
Prober: sm.CheckTypeBrowser.String(),
4642
Script: k6runner.Script{
@@ -52,20 +48,14 @@ func NewProber(ctx context.Context, check model.Check, logger zerolog.Logger, ru
5248
},
5349
}
5450

55-
if secretStore != nil {
56-
p.module.Script.SecretStore = k6runner.SecretStore{
57-
Url: secretStore.Url,
58-
Token: secretStore.Token,
59-
}
60-
}
61-
6251
processor, err := k6runner.NewProcessor(p.module.Script, runner)
6352
if err != nil {
6453
return p, err
6554
}
6655

6756
p.processor = processor
6857
p.logger = logger
58+
p.secretsRetriever = newCredentialsRetriever(store, check.GlobalTenantID())
6959

7060
return p, nil
7161
}
@@ -75,7 +65,14 @@ func (p Prober) Name() string {
7565
}
7666

7767
func (p Prober) Probe(ctx context.Context, target string, registry *prometheus.Registry, logger logger.Logger) (bool, float64) {
78-
success, err := p.processor.Run(ctx, registry, logger, p.logger)
68+
secretStore, err := p.secretsRetriever(ctx)
69+
70+
if err != nil {
71+
p.logger.Error().Err(err).Msg("running probe")
72+
return false, 0
73+
}
74+
75+
success, err := p.processor.Run(ctx, registry, logger, p.logger, secretStore)
7976
if err != nil {
8077
p.logger.Error().Err(err).Msg("running probe")
8178
return false, 0
@@ -84,3 +81,24 @@ func (p Prober) Probe(ctx context.Context, target string, registry *prometheus.R
8481
// TODO(mem): implement custom duration extraction.
8582
return success, 0
8683
}
84+
85+
// TODO(mem): This should probably be in the k6runner package.
86+
func newCredentialsRetriever(provider secrets.SecretProvider, tenantID model.GlobalID) func(context.Context) (k6runner.SecretStore, error) {
87+
return func(ctx context.Context) (k6runner.SecretStore, error) {
88+
var store k6runner.SecretStore
89+
90+
credentials, err := provider.GetSecretCredentials(ctx, tenantID)
91+
if err != nil {
92+
return store, err
93+
}
94+
95+
if credentials != nil {
96+
store = k6runner.SecretStore{
97+
Url: credentials.Url,
98+
Token: credentials.Token,
99+
}
100+
}
101+
102+
return store, nil
103+
}
104+
}

0 commit comments

Comments
 (0)