Skip to content

Commit fcfcd8f

Browse files
authored
feat: pass secret store from API (#1179)
* feat: pass secret store from API * fix: store the token in a file * fix: use the URLEncoding * fix: revert accidental change * fix: use a colon for seperation This is what the pending upstream change uses. * chore: Update the gsm extension usage to pass a config file * feat: use the -gsm prefixed bianry when secrets are enabled
1 parent 94a3d29 commit fcfcd8f

File tree

21 files changed

+483
-50
lines changed

21 files changed

+483
-50
lines changed

cmd/synthetic-monitoring-agent/main.go

+4
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ import (
3333
pusherV1 "github.com/grafana/synthetic-monitoring-agent/internal/pusher/v1"
3434
pusherV2 "github.com/grafana/synthetic-monitoring-agent/internal/pusher/v2"
3535
"github.com/grafana/synthetic-monitoring-agent/internal/scraper"
36+
"github.com/grafana/synthetic-monitoring-agent/internal/secrets"
3637
"github.com/grafana/synthetic-monitoring-agent/internal/telemetry"
3738
"github.com/grafana/synthetic-monitoring-agent/internal/tenants"
3839
"github.com/grafana/synthetic-monitoring-agent/internal/version"
@@ -288,6 +289,7 @@ func run(args []string, stdout io.Writer) error {
288289

289290
publisher := publisherFactory(ctx, tm, zl.With().Str("subsystem", "publisher").Str("version", config.SelectedPublisher).Logger(), promRegisterer)
290291
limits := limits.NewTenantLimits(tm)
292+
secrets := secrets.NewTenantSecrets(tm, zl.With().Str("subsystem", "secretstore").Logger())
291293

292294
telemetry := telemetry.NewTelemeter(
293295
ctx, uuid.New().String(), time.Duration(config.TelemetryTimeSpan)*time.Minute,
@@ -308,6 +310,7 @@ func run(args []string, stdout io.Writer) error {
308310
K6Runner: k6Runner,
309311
ScraperFactory: scraper.New,
310312
TenantLimits: limits,
313+
TenantSecrets: secrets,
311314
Telemeter: telemetry,
312315
})
313316
if err != nil {
@@ -327,6 +330,7 @@ func run(args []string, stdout io.Writer) error {
327330
PromRegisterer: promRegisterer,
328331
Features: features,
329332
K6Runner: k6Runner,
333+
TenantSecrets: secrets,
330334
})
331335
if err != nil {
332336
return fmt.Errorf("Cannot create ad-hoc checks handler: %w", err)

internal/adhoc/adhoc.go

+4-1
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ import (
88
"io"
99
"time"
1010

11+
"github.com/grafana/synthetic-monitoring-agent/internal/secrets"
12+
1113
"github.com/prometheus/client_golang/prometheus"
1214
"github.com/prometheus/prometheus/prompb"
1315
"github.com/rs/zerolog"
@@ -107,6 +109,7 @@ type HandlerOpts struct {
107109
PromRegisterer prometheus.Registerer
108110
Features feature.Collection
109111
K6Runner k6runner.Runner
112+
TenantSecrets *secrets.TenantSecrets
110113

111114
// these two fields exists so that tests can pass alternate
112115
// implementations, they are unexported so that clients of this
@@ -139,7 +142,7 @@ func NewHandler(opts HandlerOpts) (*Handler, error) {
139142
tenantCh: opts.TenantCh,
140143
runnerFactory: opts.runnerFactory,
141144
grpcAdhocChecksClientFactory: opts.grpcAdhocChecksClientFactory,
142-
proberFactory: prober.NewProberFactory(opts.K6Runner, 0, opts.Features),
145+
proberFactory: prober.NewProberFactory(opts.K6Runner, 0, opts.Features, opts.TenantSecrets),
143146
api: apiInfo{
144147
conn: opts.Conn,
145148
},

internal/checks/checks.go

+5-1
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import (
2929
"github.com/grafana/synthetic-monitoring-agent/internal/model"
3030
"github.com/grafana/synthetic-monitoring-agent/internal/pusher"
3131
"github.com/grafana/synthetic-monitoring-agent/internal/scraper"
32+
"github.com/grafana/synthetic-monitoring-agent/internal/secrets"
3233
"github.com/grafana/synthetic-monitoring-agent/internal/telemetry"
3334
"github.com/grafana/synthetic-monitoring-agent/internal/version"
3435
"github.com/grafana/synthetic-monitoring-agent/pkg/pb/synthetic_monitoring"
@@ -79,6 +80,7 @@ type Updater struct {
7980
k6Runner k6runner.Runner
8081
scraperFactory scraper.Factory
8182
tenantLimits *limits.TenantLimits
83+
tenantSecrets *secrets.TenantSecrets
8284
telemeter *telemetry.Telemeter
8385
}
8486

@@ -114,6 +116,7 @@ type UpdaterOptions struct {
114116
ScraperFactory scraper.Factory
115117
TenantLimits *limits.TenantLimits
116118
Telemeter *telemetry.Telemeter
119+
TenantSecrets *secrets.TenantSecrets
117120
}
118121

119122
func NewUpdater(opts UpdaterOptions) (*Updater, error) {
@@ -235,6 +238,7 @@ func NewUpdater(opts UpdaterOptions) (*Updater, error) {
235238
k6Runner: opts.K6Runner,
236239
scraperFactory: scraperFactory,
237240
tenantLimits: opts.TenantLimits,
241+
tenantSecrets: opts.TenantSecrets,
238242
telemeter: opts.Telemeter,
239243
metrics: metrics{
240244
changeErrorsCounter: changeErrorsCounter,
@@ -896,7 +900,7 @@ func (c *Updater) addAndStartScraperWithLock(ctx context.Context, check model.Ch
896900
c.logger,
897901
metrics,
898902
c.k6Runner,
899-
c.tenantLimits, c.telemeter,
903+
c.tenantLimits, c.telemeter, c.tenantSecrets,
900904
)
901905
if err != nil {
902906
return fmt.Errorf("cannot create new scraper: %w", err)

internal/checks/checks_test.go

+3
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ import (
88
"testing"
99
"time"
1010

11+
"github.com/grafana/synthetic-monitoring-agent/internal/secrets"
12+
1113
"github.com/prometheus/client_golang/prometheus"
1214
"github.com/prometheus/client_golang/prometheus/testutil"
1315
"github.com/rs/zerolog"
@@ -471,6 +473,7 @@ func testScraperFactory(ctx context.Context, check model.Check, publisher pusher
471473
k6Runner k6runner.Runner,
472474
labelsLimiter scraper.LabelsLimiter,
473475
telemeter *telemetry.Telemeter,
476+
secretStore *secrets.TenantSecrets,
474477
) (*scraper.Scraper, error) {
475478
return scraper.NewWithOpts(
476479
ctx,

internal/k6runner/k6runner.go

+12
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,21 @@ type Script struct {
2828
Settings Settings `json:"settings"`
2929
// CheckInfo holds information about the SM check that triggered this script.
3030
CheckInfo CheckInfo `json:"check"`
31+
// SecretStore holds the location and token for accessing secrets
32+
SecretStore SecretStore `json:"secretStore"`
3133
// TODO: Add features.
3234
}
3335

36+
type SecretStore struct {
37+
Url string `json:"url"`
38+
Token string `json:"token"`
39+
}
40+
41+
// IsConfigured returns true if the SecretStore has both URL and token configured.
42+
func (s SecretStore) IsConfigured() bool {
43+
return s.Url != "" && s.Token != ""
44+
}
45+
3446
// Settings is a common representation of the fields common to all implementation-specific check settings that the
3547
// runners are interested about.
3648
type Settings struct {

internal/k6runner/local.go

+106-28
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"bufio"
55
"bytes"
66
"context"
7+
"encoding/json"
78
"errors"
89
"fmt"
910
"io"
@@ -16,6 +17,12 @@ import (
1617
"github.com/spf13/afero"
1718
)
1819

20+
// secretSourceConfig represents the configuration for the secrets store
21+
type secretSourceConfig struct {
22+
URL string `json:"url"`
23+
Token string `json:"token"`
24+
}
25+
1926
type Local struct {
2027
k6path string
2128
logger *zerolog.Logger
@@ -68,7 +75,18 @@ func (r Local) Run(ctx context.Context, script Script) (*RunResponse, error) {
6875
return nil, fmt.Errorf("cannot write temporary script file: %w", err)
6976
}
7077

71-
k6Path, err := exec.LookPath(r.k6path)
78+
executable := r.k6path
79+
// TODO(d0ugal): This is a short-term hack to use a different k6 binary when
80+
// secrets are configured. This should be removed when the default binary has been updated
81+
if script.SecretStore.IsConfigured() {
82+
executable = r.k6path + "-gsm"
83+
logger.Info().
84+
Str("k6_path", r.k6path).
85+
Str("k6_gsm_path", executable).
86+
Msg("Using k6-gsm binary for secrets")
87+
}
88+
89+
k6Path, err := exec.LookPath(executable)
7290
if err != nil {
7391
return nil, fmt.Errorf("cannot find k6 executable: %w", err)
7492
}
@@ -77,37 +95,21 @@ func (r Local) Run(ctx context.Context, script Script) (*RunResponse, error) {
7795
ctx, cancel = context.WithTimeout(ctx, checkTimeout)
7896
defer cancel()
7997

80-
args := []string{
81-
"run",
82-
"--out", "sm=" + metricsFn,
83-
"--log-format", "logfmt",
84-
"--log-output", "file=" + logsFn,
85-
"--max-redirects", "10",
86-
"--batch", "10",
87-
"--batch-per-host", "4",
88-
"--no-connection-reuse",
89-
"--blacklist-ip", r.blacklistedIP,
90-
"--block-hostnames", "*.cluster.local", // TODO(mem): make this configurable
91-
"--summary-time-unit", "s",
92-
// "--discard-response-bodies", // TODO(mem): make this configurable
93-
"--dns", "ttl=30s,select=random,policy=preferIPv4", // TODO(mem): this needs fixing, preferIPv4 is probably not what we want
94-
"--address", "", // Disable REST API server
95-
"--no-thresholds",
96-
"--no-usage-report",
97-
"--no-color",
98-
"--no-summary",
99-
"--verbose",
98+
var configFile string
99+
if script.SecretStore.IsConfigured() {
100+
var cleanup func()
101+
configFile, cleanup, err = createSecretConfigFile(script.SecretStore.Url, script.SecretStore.Token)
102+
if err != nil {
103+
return nil, fmt.Errorf("cannot create secret config file: %w", err)
104+
}
105+
defer cleanup()
100106
}
101107

102-
if script.CheckInfo.Type != synthetic_monitoring.CheckTypeBrowser.String() {
103-
args = append(args,
104-
"--vus", "1",
105-
"--iterations", "1",
106-
)
108+
args, err := r.buildK6Args(script, metricsFn, logsFn, scriptFn, configFile)
109+
if err != nil {
110+
return nil, fmt.Errorf("building k6 arguments: %w", err)
107111
}
108112

109-
args = append(args, scriptFn)
110-
111113
cmd := exec.CommandContext(
112114
ctx,
113115
k6Path,
@@ -187,6 +189,46 @@ func (r Local) Run(ctx context.Context, script Script) (*RunResponse, error) {
187189
return &RunResponse{Metrics: metrics.Bytes(), Logs: logs.Bytes()}, errors.Join(err, errorFromLogs(logs.Bytes()))
188190
}
189191

192+
func (r Local) buildK6Args(script Script, metricsFn, logsFn, scriptFn, configFile string) ([]string, error) {
193+
args := []string{
194+
"run",
195+
"--out", "sm=" + metricsFn,
196+
"--log-format", "logfmt",
197+
"--log-output", "file=" + logsFn,
198+
"--max-redirects", "10",
199+
"--batch", "10",
200+
"--batch-per-host", "4",
201+
"--no-connection-reuse",
202+
"--blacklist-ip", r.blacklistedIP,
203+
"--block-hostnames", "*.cluster.local", // TODO(mem): make this configurable
204+
"--summary-time-unit", "s",
205+
// "--discard-response-bodies", // TODO(mem): make this configurable
206+
"--dns", "ttl=30s,select=random,policy=preferIPv4", // TODO(mem): this needs fixing, preferIPv4 is probably not what we want
207+
"--address", "", // Disable REST API server
208+
"--no-thresholds",
209+
"--no-usage-report",
210+
"--no-color",
211+
"--no-summary",
212+
"--verbose",
213+
}
214+
215+
// Add secretStore configuration if available
216+
if script.SecretStore.IsConfigured() {
217+
args = append(args, "--secret-source", "grafanasecrets=config="+configFile)
218+
}
219+
220+
if script.CheckInfo.Type != synthetic_monitoring.CheckTypeBrowser.String() {
221+
args = append(args,
222+
"--vus", "1",
223+
"--iterations", "1",
224+
)
225+
}
226+
227+
args = append(args, scriptFn)
228+
229+
return args, nil
230+
}
231+
190232
func mktemp(fs afero.Fs, dir, pattern string) (string, error) {
191233
f, err := afero.TempFile(fs, dir, pattern)
192234
if err != nil {
@@ -262,3 +304,39 @@ func readFileLimit(f afero.Fs, name string, limit int64) (*bytes.Buffer, bool, e
262304

263305
return buf, true, nil
264306
}
307+
308+
// createSecretConfigFile creates a JSON config file with the given secret store URL and token
309+
func createSecretConfigFile(url, token string) (filename string, cleanup func(), err error) {
310+
tmpFile, err := os.CreateTemp("", "k6-secrets-*.json")
311+
if err != nil {
312+
return "", nil, fmt.Errorf("creating temp file: %w", err)
313+
}
314+
315+
if err := os.Chmod(tmpFile.Name(), 0600); err != nil {
316+
os.Remove(tmpFile.Name())
317+
return "", nil, fmt.Errorf("setting file permissions: %w", err)
318+
}
319+
320+
config := secretSourceConfig{
321+
URL: url,
322+
Token: token,
323+
}
324+
325+
configData, err := json.Marshal(config)
326+
if err != nil {
327+
os.Remove(tmpFile.Name())
328+
return "", nil, fmt.Errorf("marshaling config to JSON: %w", err)
329+
}
330+
331+
if _, err := tmpFile.Write(configData); err != nil {
332+
os.Remove(tmpFile.Name())
333+
return "", nil, fmt.Errorf("writing config file: %w", err)
334+
}
335+
336+
if err := tmpFile.Close(); err != nil {
337+
os.Remove(tmpFile.Name())
338+
return "", nil, fmt.Errorf("closing config file: %w", err)
339+
}
340+
341+
return tmpFile.Name(), func() { os.Remove(tmpFile.Name()) }, nil
342+
}

0 commit comments

Comments
 (0)