@cryptotaxi247 / netdata-1 / commits / f09bef03f

feat(plugin/agent): add configurable secretstore backend HTTP timeouts (#22118)

Ilya Mashchenko committed Apr 3, 2026 at 07:05 UTC f09bef03f98010e26cbc7e1abccb70b3656402d2
28 files changed +527 -149
src/go/plugin/agent/secrets/secretstore/backends/aws/config_schema.json
+7
@@ -19,6 +19,13 @@
19 "title": "Region",
20 "description": "AWS region used for Secrets Manager requests.",
21 "type": "string"
22 + },
23 + "timeout": {
24 + "title": "Timeout",
25 + "description": "Timeout in seconds for HTTP requests made by this secretstore backend.",
26 + "type": "number",
27 + "minimum": 0,
28 + "default": 3
29 }
30 },
31 "required": [
src/go/plugin/agent/secrets/secretstore/backends/aws/init.go
+13 -1
@@ -8,6 +8,7 @@ import (
8 "strings"
9
10 "github.com/netdata/netdata/go/plugins/plugin/agent/secrets/secretstore"
11 + "github.com/netdata/netdata/go/plugins/plugin/agent/secrets/secretstore/internal/httpx"
12 )
13
14 func (s *store) init(_ context.Context) error {
@@ -24,8 +25,19 @@ func (s *store) init(_ context.Context) error {
25 }
26 s.Config.Region = region
27
28 + switch {
29 + case s.Config.Timeout.Duration() < 0:
30 + return fmt.Errorf("timeout cannot be negative")
31 + case s.Config.Timeout.Duration() == 0:
32 + s.Config.Timeout = defaultTimeout
33 + }
34 + s.runtime = &runtime{
35 + apiClient: httpx.APIClient(s.Config.Timeout.Duration()),
36 + imdsClient: httpx.NoProxyClient(s.Config.Timeout.Duration()),
37 + }
38 +
39 published := &publishedStore{
28 - provider: s.provider,
40 + runtime: s.runtime,
41 mode: s.Config.AuthMode,
42 regionValue: region,
43 }
src/go/plugin/agent/secrets/secretstore/backends/aws/init_test.go new
+83
@@ -0,0 +1,83 @@
1 +// SPDX-License-Identifier: GPL-3.0-or-later
2 +
3 +package aws
4 +
5 +import (
6 + "context"
7 + "testing"
8 + "time"
9 +
10 + "github.com/netdata/netdata/go/plugins/pkg/confopt"
11 + "github.com/stretchr/testify/assert"
12 + "github.com/stretchr/testify/require"
13 +)
14 +
15 +func TestStoreInitTimeout(t *testing.T) {
16 + tests := map[string]struct {
17 + timeout confopt.Duration
18 + wantTimeout time.Duration
19 + wantErrContains string
20 + }{
21 + "default timeout": {
22 + wantTimeout: defaultTimeout.Duration(),
23 + },
24 + "configured timeout": {
25 + timeout: confopt.Duration(7 * time.Second),
26 + wantTimeout: 7 * time.Second,
27 + },
28 + "negative timeout": {
29 + timeout: confopt.Duration(-time.Second),
30 + wantErrContains: "timeout cannot be negative",
31 + },
32 + }
33 +
34 + for name, tc := range tests {
35 + t.Run(name, func(t *testing.T) {
36 + s := &store{
37 + Config: Config{
38 + AuthMode: "env",
39 + Region: "us-east-1",
40 + Timeout: tc.timeout,
41 + },
42 + }
43 +
44 + err := s.init(context.Background())
45 + if tc.wantErrContains != "" {
46 + require.Error(t, err)
47 + assert.ErrorContains(t, err, tc.wantErrContains)
48 + return
49 + }
50 +
51 + require.NoError(t, err)
52 + assert.Equal(t, tc.wantTimeout, s.runtime.apiClient.Timeout)
53 + assert.Equal(t, tc.wantTimeout, s.runtime.imdsClient.Timeout)
54 + assert.Equal(t, confopt.Duration(tc.wantTimeout), s.Config.Timeout)
55 + })
56 + }
57 +}
58 +
59 +func TestInitBuildsStoreScopedRuntime(t *testing.T) {
60 + creator := New()
61 +
62 + first, ok := creator.Create().(*store)
63 + require.True(t, ok)
64 + second, ok := creator.Create().(*store)
65 + require.True(t, ok)
66 +
67 + assert.Equal(t, defaultTimeout, first.Config.Timeout)
68 + first.AuthMode = "env"
69 + first.Region = "us-east-1"
70 + second.AuthMode = "env"
71 + second.Region = "us-east-1"
72 +
73 + require.NoError(t, first.init(context.Background()))
74 + require.NoError(t, second.init(context.Background()))
75 +
76 + require.NotNil(t, first.runtime)
77 + require.NotNil(t, second.runtime)
78 + assert.NotSame(t, first.runtime, second.runtime)
79 + assert.NotSame(t, first.runtime.apiClient, second.runtime.apiClient)
80 + assert.NotSame(t, first.runtime.imdsClient, second.runtime.imdsClient)
81 + assert.Equal(t, defaultTimeout.Duration(), first.runtime.apiClient.Timeout)
82 + assert.Equal(t, defaultTimeout.Duration(), first.runtime.imdsClient.Timeout)
83 +}
src/go/plugin/agent/secrets/secretstore/backends/aws/metadata.yaml
+4
@@ -62,6 +62,10 @@ setup:
62 description: 'AWS region used for Secrets Manager requests. There is no automatic region detection — you must always set this explicitly.'
63 default_value: ''
64 required: true
65 + - name: 'timeout'
66 + description: 'Timeout in seconds for HTTP requests made by this secretstore backend.'
67 + default_value: 3
68 + required: false
69 examples:
70 folding:
71 title: 'Example configuration'
src/go/plugin/agent/secrets/secretstore/backends/aws/provider.go
+16 -19
@@ -8,16 +8,19 @@ import (
8 "net/http"
9 "time"
10
11 + "github.com/netdata/netdata/go/plugins/pkg/confopt"
12 "github.com/netdata/netdata/go/plugins/plugin/agent/secrets/secretstore"
12 - "github.com/netdata/netdata/go/plugins/plugin/agent/secrets/secretstore/internal/httpx"
13 )
14
15 //go:embed config_schema.json
16 var configSchema string
17
18 +var defaultTimeout = confopt.Duration(3 * time.Second)
19 +
20 type Config struct {
19 - AuthMode string `json:"auth_mode" yaml:"auth_mode"`
20 - Region string `json:"region" yaml:"region"`
21 + AuthMode string `json:"auth_mode" yaml:"auth_mode"`
22 + Region string `json:"region" yaml:"region"`
23 + Timeout confopt.Duration `json:"timeout,omitempty" yaml:"timeout,omitempty"`
24 }
25
26 type credentials struct {
@@ -26,44 +29,38 @@ type credentials struct {
29 sessionToken string
30 }
31
29 -type provider struct {
32 +type runtime struct {
33 apiClient *http.Client
34 imdsClient *http.Client
32 - endpoint string
33 - now func() time.Time
35 }
36
37 type store struct {
38 Config `yaml:",inline" json:""`
38 - provider *provider
39 + runtime *runtime
40 published *publishedStore
41 }
42
43 type publishedStore struct {
43 - provider *provider
44 + runtime *runtime
45 mode string
46 regionValue string
47 }
48
49 func New() secretstore.Creator {
49 - p := &provider{
50 - apiClient: httpx.APIClient(10 * time.Second),
51 - imdsClient: httpx.NoProxyClient(2 * time.Second),
52 - now: time.Now,
53 - }
54 -
50 return secretstore.Creator{
51 Kind: secretstore.KindAWSSM,
52 DisplayName: "AWS Secrets Manager",
53 Schema: configSchema,
59 - Create: p.create,
54 + Create: func() secretstore.Store {
55 + return &store{
56 + Config: Config{
57 + Timeout: defaultTimeout,
58 + },
59 + }
60 + },
61 }
62 }
63
63 -func (p *provider) create() secretstore.Store {
64 - return &store{provider: p}
65 -}
66 -
64 func (s *store) Configuration() any { return &s.Config }
65
66 func (s *store) Init(ctx context.Context) error { return s.init(ctx) }
src/go/plugin/agent/secrets/secretstore/backends/aws/resolve.go
+13 -10
@@ -11,8 +11,10 @@ import (
11 "fmt"
12 "io"
13 "net/http"
14 + "net/url"
15 "sort"
16 "strings"
17 + "time"
18
19 "github.com/netdata/netdata/go/plugins/logger"
20 "github.com/netdata/netdata/go/plugins/plugin/agent/secrets/secretstore"
@@ -124,7 +126,7 @@ func (s *publishedStore) ecsCredentials(ctx context.Context, relativeURI string)
126 if err != nil {
127 return nil, fmt.Errorf("creating ECS credentials request: %w", err)
128 }
127 - resp, err := s.provider.imdsClient.Do(req)
129 + resp, err := s.runtime.imdsClient.Do(req)
130 if err != nil {
131 return nil, fmt.Errorf("ECS credentials request failed: %w", err)
132 }
@@ -160,7 +162,7 @@ func (s *publishedStore) imdsCredentials(ctx context.Context) (*credentials, err
162 return nil, fmt.Errorf("creating IMDS token request: %w", err)
163 }
164 tokenReq.Header.Set("X-aws-ec2-metadata-token-ttl-seconds", "21600")
163 - tokenResp, err := s.provider.imdsClient.Do(tokenReq)
165 + tokenResp, err := s.runtime.imdsClient.Do(tokenReq)
166 if err != nil {
167 return nil, fmt.Errorf("IMDS token request failed: %w", err)
168 }
@@ -179,7 +181,7 @@ func (s *publishedStore) imdsCredentials(ctx context.Context) (*credentials, err
181 return nil, fmt.Errorf("creating IMDS role request: %w", err)
182 }
183 roleReq.Header.Set("X-aws-ec2-metadata-token", imdsToken)
182 - roleResp, err := s.provider.imdsClient.Do(roleReq)
184 + roleResp, err := s.runtime.imdsClient.Do(roleReq)
185 if err != nil {
186 return nil, fmt.Errorf("IMDS role request failed: %w", err)
187 }
@@ -201,7 +203,7 @@ func (s *publishedStore) imdsCredentials(ctx context.Context) (*credentials, err
203 return nil, fmt.Errorf("creating IMDS credentials request: %w", err)
204 }
205 credReq.Header.Set("X-aws-ec2-metadata-token", imdsToken)
204 - credResp, err := s.provider.imdsClient.Do(credReq)
206 + credResp, err := s.runtime.imdsClient.Do(credReq)
207 if err != nil {
208 return nil, fmt.Errorf("IMDS credentials request failed: %w", err)
209 }
@@ -228,17 +230,18 @@ func (s *publishedStore) imdsCredentials(ctx context.Context) (*credentials, err
230 }
231
232 func (s *publishedStore) secretValue(ctx context.Context, creds *credentials, region, secretName, original string) (string, error) {
231 - endpoint := s.provider.endpoint
233 host := secretsManagerHost(region)
233 - if endpoint == "" {
234 - endpoint = "https://" + host + "/"
235 - }
234 + endpoint := (&url.URL{
235 + Scheme: "https",
236 + Host: host,
237 + Path: "/",
238 + }).String()
239 secretIDJSON, err := json.Marshal(secretName)
240 if err != nil {
241 return "", fmt.Errorf("resolving secret '%s': encoding secret name: %w", original, err)
242 }
243 payload := `{"SecretId":` + string(secretIDJSON) + `}`
241 - now := s.provider.now().UTC()
244 + now := time.Now().UTC()
245 timestamp := now.Format("20060102T150405Z")
246 datestamp := now.Format("20060102")
247 headers := map[string]string{
@@ -260,7 +263,7 @@ func (s *publishedStore) secretValue(ctx context.Context, creds *credentials, re
263 }
264 httpReq.Host = host
265 httpReq.Header.Set("Authorization", authHeader)
263 - resp, err := s.provider.apiClient.Do(httpReq)
266 + resp, err := s.runtime.apiClient.Do(httpReq)
267 if err != nil {
268 return "", fmt.Errorf("resolving secret '%s': request failed: %w", original, err)
269 }
src/go/plugin/agent/secrets/secretstore/backends/aws/resolve_test.go
+12 -21
@@ -7,9 +7,7 @@ import (
7 "context"
8 "io"
9 "net/http"
10 - "net/http/httptest"
10 "testing"
12 - "time"
11
12 "github.com/netdata/netdata/go/plugins/logger"
13 "github.com/netdata/netdata/go/plugins/plugin/agent/secrets/secretstore"
@@ -45,7 +43,7 @@ func TestSecretsManagerHost(t *testing.T) {
43 }
44 }
45
48 -func TestSecretValue_CustomEndpointUsesSignedHostHeader(t *testing.T) {
46 +func TestSecretValue_UsesSignedHostHeader(t *testing.T) {
47 tests := map[string]struct {
48 region string
49 wantHost string
@@ -62,20 +60,16 @@ func TestSecretValue_CustomEndpointUsesSignedHostHeader(t *testing.T) {
60
61 for name, tc := range tests {
62 t.Run(name, func(t *testing.T) {
65 - srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
66 - assert.Equal(t, tc.wantHost, r.Host)
67 - w.Header().Set("Content-Type", "application/x-amz-json-1.1")
68 - _, _ = w.Write([]byte(`{"SecretString":"value"}`))
69 - }))
70 - defer srv.Close()
71 -
63 store := &publishedStore{
73 - provider: &provider{
74 - apiClient: srv.Client(),
75 - endpoint: srv.URL + "/",
76 - now: func() time.Time {
77 - return time.Date(2026, time.March, 18, 12, 0, 0, 0, time.UTC)
78 - },
64 + runtime: &runtime{
65 + apiClient: &http.Client{Transport: roundTripFunc(func(r *http.Request) (*http.Response, error) {
66 + assert.Equal(t, tc.wantHost, r.Host)
67 + return &http.Response{
68 + StatusCode: http.StatusOK,
69 + Body: io.NopCloser(bytes.NewBufferString(`{"SecretString":"value"}`)),
70 + Header: make(http.Header),
71 + }, nil
72 + })},
73 },
74 }
75
@@ -94,18 +88,15 @@ func TestPublishedStoreResolve_LogsDetailedResolution(t *testing.T) {
88 t.Setenv("AWS_SECRET_ACCESS_KEY", "SECRET")
89
90 store := &publishedStore{
97 - provider: &provider{
91 + runtime: &runtime{
92 apiClient: &http.Client{Transport: roundTripFunc(func(req *http.Request) (*http.Response, error) {
93 + assert.Equal(t, "secretsmanager.us-east-1.amazonaws.com", req.Host)
94 return &http.Response{
95 StatusCode: http.StatusOK,
96 Body: io.NopCloser(bytes.NewBufferString(`{"SecretString":"{\"password\":\"secret-value\"}"}`)),
97 Header: make(http.Header),
98 }, nil
99 })},
105 - endpoint: "https://example.test/",
106 - now: func() time.Time {
107 - return time.Date(2026, time.March, 18, 12, 0, 0, 0, time.UTC)
108 - },
100 },
101 mode: "env",
102 regionValue: "us-east-1",
src/go/plugin/agent/secrets/secretstore/backends/azure/config_schema.json
+7
@@ -14,6 +14,13 @@
14 "default"
15 ],
16 "default": "default"
17 + },
18 + "timeout": {
19 + "title": "Timeout",
20 + "description": "Timeout in seconds for HTTP requests made by this secretstore backend.",
21 + "type": "number",
22 + "minimum": 0,
23 + "default": 3
24 }
25 },
26 "required": [
src/go/plugin/agent/secrets/secretstore/backends/azure/init.go
+27 -15
@@ -12,15 +12,27 @@ import (
12 "github.com/Azure/azure-sdk-for-go/sdk/azcore"
13 "github.com/Azure/azure-sdk-for-go/sdk/azcore/policy"
14 "github.com/netdata/netdata/go/plugins/plugin/agent/secrets/secretstore"
15 + "github.com/netdata/netdata/go/plugins/plugin/agent/secrets/secretstore/internal/httpx"
16 "github.com/netdata/netdata/go/plugins/plugin/go.d/pkg/cloudauth"
17 )
18
19 const azureKeyVaultScope = "https://vault.azure.net/.default"
20
21 func (s *store) init(_ context.Context) error {
22 + switch {
23 + case s.Config.Timeout.Duration() < 0:
24 + return fmt.Errorf("timeout cannot be negative")
25 + case s.Config.Timeout.Duration() == 0:
26 + s.Config.Timeout = defaultTimeout
27 + }
28 +
29 if err := s.Config.ValidateWithPath(""); err != nil {
30 return err
31 }
32 + s.runtime = &runtime{
33 + apiClient: httpx.APIClient(s.Config.Timeout.Duration()),
34 + imdsClient: httpx.NoProxyClient(s.Config.Timeout.Duration()),
35 + }
36
37 cred, err := s.Config.NewCredentialWithOptions(s.credentialOptions())
38 if err != nil {
@@ -41,7 +53,7 @@ func (s *store) init(_ context.Context) error {
53 }
54
55 s.published = &publishedStore{
44 - provider: s.provider,
56 + runtime: s.runtime,
57 tokenProvider: tokenProvider,
58 }
59 return nil
@@ -50,19 +62,19 @@ func (s *store) init(_ context.Context) error {
62 func (s *store) authTimeout() time.Duration {
63 switch s.Config.NormalizedMode() {
64 case cloudauth.AzureADAuthModeServicePrincipal:
53 - if s.provider.apiClient != nil {
54 - return s.provider.apiClient.Timeout
65 + if s.runtime.apiClient != nil {
66 + return s.runtime.apiClient.Timeout
67 }
68 case cloudauth.AzureADAuthModeManagedIdentity:
57 - if s.provider.imdsClient != nil {
58 - return s.provider.imdsClient.Timeout
69 + if s.runtime.imdsClient != nil {
70 + return s.runtime.imdsClient.Timeout
71 }
72 case cloudauth.AzureADAuthModeDefault:
61 - if s.provider.apiClient != nil && s.provider.apiClient.Timeout > 0 {
62 - return s.provider.apiClient.Timeout
73 + if s.runtime.apiClient != nil && s.runtime.apiClient.Timeout > 0 {
74 + return s.runtime.apiClient.Timeout
75 }
64 - if s.provider.imdsClient != nil {
65 - return s.provider.imdsClient.Timeout
76 + if s.runtime.imdsClient != nil {
77 + return s.runtime.imdsClient.Timeout
78 }
79 }
80
@@ -74,17 +86,17 @@ func (s *store) credentialOptions() *cloudauth.AzureADCredentialOptions {
86
87 switch s.Config.NormalizedMode() {
88 case cloudauth.AzureADAuthModeServicePrincipal:
77 - if s.provider.apiClient != nil && s.provider.apiClient.Transport != nil {
78 - opts.ClientOptions.Transport = transportAdapter{s.provider.apiClient.Transport}
89 + if s.runtime.apiClient != nil && s.runtime.apiClient.Transport != nil {
90 + opts.ClientOptions.Transport = transportAdapter{s.runtime.apiClient.Transport}
91 }
92 case cloudauth.AzureADAuthModeManagedIdentity:
81 - if s.provider.imdsClient != nil && s.provider.imdsClient.Transport != nil {
82 - opts.ClientOptions.Transport = transportAdapter{s.provider.imdsClient.Transport}
93 + if s.runtime.imdsClient != nil && s.runtime.imdsClient.Transport != nil {
94 + opts.ClientOptions.Transport = transportAdapter{s.runtime.imdsClient.Transport}
95 }
96 case cloudauth.AzureADAuthModeDefault:
97 opts.ClientOptions.Transport = routingTransportAdapter{
86 - defaultRoundTripper: roundTripperForClient(s.provider.apiClient),
87 - noProxyRoundTripper: roundTripperForClient(s.provider.imdsClient),
98 + defaultRoundTripper: roundTripperForClient(s.runtime.apiClient),
99 + noProxyRoundTripper: roundTripperForClient(s.runtime.imdsClient),
100 }
101 }
102
src/go/plugin/agent/secrets/secretstore/backends/azure/init_test.go
+68 -18
@@ -11,6 +11,7 @@ import (
11
12 "github.com/Azure/azure-sdk-for-go/sdk/azcore"
13 "github.com/Azure/azure-sdk-for-go/sdk/azcore/policy"
14 + "github.com/netdata/netdata/go/plugins/pkg/confopt"
15 "github.com/netdata/netdata/go/plugins/plugin/go.d/pkg/cloudauth"
16 "github.com/stretchr/testify/assert"
17 "github.com/stretchr/testify/require"
@@ -19,43 +20,58 @@ import (
20 func TestStoreInit(t *testing.T) {
21 tests := map[string]struct {
22 cfg Config
23 + wantTimeout time.Duration
24 wantErrContains string
25 }{
26 "service principal": {
27 cfg: Config{
26 - Mode: cloudauth.AzureADAuthModeServicePrincipal,
27 - ModeServicePrincipal: &cloudauth.AzureADModeServicePrincipalConfig{
28 - TenantID: "tenant-id",
29 - ClientID: "client-id",
30 - ClientSecret: "client-secret",
28 + AzureADAuthConfig: cloudauth.AzureADAuthConfig{
29 + Mode: cloudauth.AzureADAuthModeServicePrincipal,
30 + ModeServicePrincipal: &cloudauth.AzureADModeServicePrincipalConfig{
31 + TenantID: "tenant-id",
32 + ClientID: "client-id",
33 + ClientSecret: "client-secret",
34 + },
35 },
36 + Timeout: confopt.Duration(7 * time.Second),
37 },
38 + wantTimeout: 7 * time.Second,
39 },
40 "default": {
41 cfg: Config{
36 - Mode: cloudauth.AzureADAuthModeDefault,
42 + AzureADAuthConfig: cloudauth.AzureADAuthConfig{
43 + Mode: cloudauth.AzureADAuthModeDefault,
44 + },
45 },
46 + wantTimeout: defaultTimeout.Duration(),
47 },
48 "service principal validation": {
49 cfg: Config{
41 - Mode: cloudauth.AzureADAuthModeServicePrincipal,
42 - ModeServicePrincipal: &cloudauth.AzureADModeServicePrincipalConfig{
43 - TenantID: "tenant-id",
44 - ClientID: "client-id",
50 + AzureADAuthConfig: cloudauth.AzureADAuthConfig{
51 + Mode: cloudauth.AzureADAuthModeServicePrincipal,
52 + ModeServicePrincipal: &cloudauth.AzureADModeServicePrincipalConfig{
53 + TenantID: "tenant-id",
54 + ClientID: "client-id",
55 + },
56 },
57 },
58 wantErrContains: "mode_service_principal.client_secret is required",
59 },
60 + "negative timeout": {
61 + cfg: Config{
62 + AzureADAuthConfig: cloudauth.AzureADAuthConfig{
63 + Mode: cloudauth.AzureADAuthModeDefault,
64 + },
65 + Timeout: confopt.Duration(-time.Second),
66 + },
67 + wantErrContains: "timeout cannot be negative",
68 + },
69 }
70
71 for name, tc := range tests {
72 t.Run(name, func(t *testing.T) {
73 s := &store{
74 Config: tc.cfg,
55 - provider: &provider{
56 - apiClient: &http.Client{},
57 - imdsClient: &http.Client{},
58 - },
75 }
76
77 err := s.init(context.Background())
@@ -68,6 +84,8 @@ func TestStoreInit(t *testing.T) {
84 require.NoError(t, err)
85 require.NotNil(t, s.published)
86 assert.NotNil(t, s.published.tokenProvider)
87 + assert.Equal(t, tc.wantTimeout, s.runtime.apiClient.Timeout)
88 + assert.Equal(t, tc.wantTimeout, s.runtime.imdsClient.Timeout)
89 })
90 }
91 }
@@ -100,8 +118,12 @@ func TestStoreAuthTimeout(t *testing.T) {
118 for name, tc := range tests {
119 t.Run(name, func(t *testing.T) {
120 s := &store{
103 - Config: Config{Mode: tc.mode},
104 - provider: &provider{
121 + Config: Config{
122 + AzureADAuthConfig: cloudauth.AzureADAuthConfig{
123 + Mode: tc.mode,
124 + },
125 + },
126 + runtime: &runtime{
127 apiClient: &http.Client{Timeout: tc.apiTimeout},
128 imdsClient: &http.Client{Timeout: tc.imdsTimeout},
129 },
@@ -221,8 +243,12 @@ func TestDefaultCredentialTransportRouting(t *testing.T) {
243 var noProxyCalls int
244
245 s := &store{
224 - Config: Config{Mode: cloudauth.AzureADAuthModeDefault},
225 - provider: &provider{
246 + Config: Config{
247 + AzureADAuthConfig: cloudauth.AzureADAuthConfig{
248 + Mode: cloudauth.AzureADAuthModeDefault,
249 + },
250 + },
251 + runtime: &runtime{
252 apiClient: &http.Client{Transport: roundTripFunc(func(*http.Request) (*http.Response, error) {
253 defaultCalls++
254 return &http.Response{StatusCode: http.StatusOK, Body: http.NoBody, Header: make(http.Header)}, nil
@@ -248,3 +274,27 @@ func TestDefaultCredentialTransportRouting(t *testing.T) {
274 })
275 }
276 }
277 +
278 +func TestInitBuildsStoreScopedRuntime(t *testing.T) {
279 + creator := New()
280 +
281 + first, ok := creator.Create().(*store)
282 + require.True(t, ok)
283 + second, ok := creator.Create().(*store)
284 + require.True(t, ok)
285 +
286 + assert.Equal(t, defaultTimeout, first.Config.Timeout)
287 + first.AzureADAuthConfig.Mode = cloudauth.AzureADAuthModeDefault
288 + second.AzureADAuthConfig.Mode = cloudauth.AzureADAuthModeDefault
289 +
290 + require.NoError(t, first.init(context.Background()))
291 + require.NoError(t, second.init(context.Background()))
292 +
293 + require.NotNil(t, first.runtime)
294 + require.NotNil(t, second.runtime)
295 + assert.NotSame(t, first.runtime, second.runtime)
296 + assert.NotSame(t, first.runtime.apiClient, second.runtime.apiClient)
297 + assert.NotSame(t, first.runtime.imdsClient, second.runtime.imdsClient)
298 + assert.Equal(t, defaultTimeout.Duration(), first.runtime.apiClient.Timeout)
299 + assert.Equal(t, defaultTimeout.Duration(), first.runtime.imdsClient.Timeout)
300 +}
src/go/plugin/agent/secrets/secretstore/backends/azure/metadata.yaml
+4
@@ -78,6 +78,10 @@ setup:
78 description: 'Optional client ID of a user-assigned managed identity when `mode` is `managed_identity`. Leave it empty for the system-assigned identity.'
79 default_value: ''
80 required: false
81 + - name: 'timeout'
82 + description: 'Timeout in seconds for HTTP requests made by this secretstore backend.'
83 + default_value: 3
84 + required: false
85 examples:
86 folding:
87 title: 'Example configuration'
src/go/plugin/agent/secrets/secretstore/backends/azure/provider.go
+17 -15
@@ -9,8 +9,8 @@ import (
9 "regexp"
10 "time"
11
12 + "github.com/netdata/netdata/go/plugins/pkg/confopt"
13 "github.com/netdata/netdata/go/plugins/plugin/agent/secrets/secretstore"
13 - "github.com/netdata/netdata/go/plugins/plugin/agent/secrets/secretstore/internal/httpx"
14 "github.com/netdata/netdata/go/plugins/plugin/go.d/pkg/cloudauth"
15 )
16
@@ -20,42 +20,44 @@ var (
20 reAzureSafeName = regexp.MustCompile(`^[a-zA-Z0-9-]+$`)
21 )
22
23 -type Config = cloudauth.AzureADAuthConfig
23 +var defaultTimeout = confopt.Duration(3 * time.Second)
24
25 -type provider struct {
25 +type Config struct {
26 + cloudauth.AzureADAuthConfig `yaml:",inline" json:",inline"`
27 + Timeout confopt.Duration `yaml:"timeout,omitempty" json:"timeout,omitempty"`
28 +}
29 +
30 +type runtime struct {
31 apiClient *http.Client
32 imdsClient *http.Client
33 }
34
35 type store struct {
36 Config `yaml:",inline" json:""`
32 - provider *provider
37 + runtime *runtime
38 published *publishedStore
39 }
40
41 type publishedStore struct {
37 - provider *provider
42 + runtime *runtime
43 tokenProvider *cloudauth.TokenProvider
44 }
45
46 func New() secretstore.Creator {
42 - p := &provider{
43 - apiClient: httpx.APIClient(10 * time.Second),
44 - imdsClient: httpx.NoProxyClient(2 * time.Second),
45 - }
46 -
47 return secretstore.Creator{
48 Kind: secretstore.KindAzureKV,
49 DisplayName: "Azure Key Vault",
50 Schema: configSchema,
51 - Create: p.create,
51 + Create: func() secretstore.Store {
52 + return &store{
53 + Config: Config{
54 + Timeout: defaultTimeout,
55 + },
56 + }
57 + },
58 }
59 }
60
55 -func (p *provider) create() secretstore.Store {
56 - return &store{provider: p}
57 -}
58 -
61 func (s *store) Configuration() any { return &s.Config }
62
63 func (s *store) Init(ctx context.Context) error { return s.init(ctx) }
src/go/plugin/agent/secrets/secretstore/backends/azure/resolve.go
+1 -1
@@ -42,7 +42,7 @@ func (s *publishedStore) resolve(ctx context.Context, req secretstore.ResolveReq
42 }
43 httpReq.Header.Set("Authorization", "Bearer "+token)
44
45 - resp, err := s.provider.apiClient.Do(httpReq)
45 + resp, err := s.runtime.apiClient.Do(httpReq)
46 if err != nil {
47 return "", fmt.Errorf("resolving secret '%s': store '%s': request failed: %w", req.Original, req.StoreKey, err)
48 }
src/go/plugin/agent/secrets/secretstore/backends/azure/resolve_test.go
+2 -2
@@ -90,7 +90,7 @@ func TestPublishedStoreResolve(t *testing.T) {
90 require.NoError(t, err)
91
92 s := &publishedStore{
93 - provider: &provider{
93 + runtime: &runtime{
94 apiClient: &http.Client{Transport: tc.transport},
95 },
96 tokenProvider: tokenProvider,
@@ -131,7 +131,7 @@ func TestPublishedStoreResolve_LogsDetailedResolution(t *testing.T) {
131 require.NoError(t, err)
132
133 s := &publishedStore{
134 - provider: &provider{
134 + runtime: &runtime{
135 apiClient: &http.Client{Transport: roundTripFunc(func(req *http.Request) (*http.Response, error) {
136 return &http.Response{
137 StatusCode: http.StatusOK,
src/go/plugin/agent/secrets/secretstore/backends/gcp/config_schema.json
+7
@@ -13,6 +13,13 @@
13 "service_account_file"
14 ],
15 "default": "metadata"
16 + },
17 + "timeout": {
18 + "title": "Timeout",
19 + "description": "Timeout in seconds for HTTP requests made by this secretstore backend.",
20 + "type": "number",
21 + "minimum": 0,
22 + "default": 3
23 }
24 },
25 "required": [
src/go/plugin/agent/secrets/secretstore/backends/gcp/init.go
+13 -1
@@ -8,10 +8,22 @@ import (
8 "strings"
9
10 "github.com/netdata/netdata/go/plugins/plugin/agent/secrets/secretstore"
11 + "github.com/netdata/netdata/go/plugins/plugin/agent/secrets/secretstore/internal/httpx"
12 )
13
14 func (s *store) init(_ context.Context) error {
14 - published := &publishedStore{provider: s.provider}
15 + switch {
16 + case s.Config.Timeout.Duration() < 0:
17 + return fmt.Errorf("timeout cannot be negative")
18 + case s.Config.Timeout.Duration() == 0:
19 + s.Config.Timeout = defaultTimeout
20 + }
21 + s.runtime = &runtime{
22 + apiClient: httpx.APIClient(s.Config.Timeout.Duration()),
23 + metadataClient: httpx.NoProxyClient(s.Config.Timeout.Duration()),
24 + }
25 +
26 + published := &publishedStore{runtime: s.runtime}
27
28 switch strings.TrimSpace(s.Config.Mode) {
29 case "metadata":
src/go/plugin/agent/secrets/secretstore/backends/gcp/init_test.go new
+80
@@ -0,0 +1,80 @@
1 +// SPDX-License-Identifier: GPL-3.0-or-later
2 +
3 +package gcp
4 +
5 +import (
6 + "context"
7 + "testing"
8 + "time"
9 +
10 + "github.com/netdata/netdata/go/plugins/pkg/confopt"
11 + "github.com/stretchr/testify/assert"
12 + "github.com/stretchr/testify/require"
13 +)
14 +
15 +func TestStoreInitTimeout(t *testing.T) {
16 + tests := map[string]struct {
17 + timeout confopt.Duration
18 + wantTimeout time.Duration
19 + wantErrContains string
20 + }{
21 + "default timeout": {
22 + wantTimeout: defaultTimeout.Duration(),
23 + },
24 + "configured timeout": {
25 + timeout: confopt.Duration(7 * time.Second),
26 + wantTimeout: 7 * time.Second,
27 + },
28 + "negative timeout": {
29 + timeout: confopt.Duration(-time.Second),
30 + wantErrContains: "timeout cannot be negative",
31 + },
32 + }
33 +
34 + for name, tc := range tests {
35 + t.Run(name, func(t *testing.T) {
36 + s := &store{
37 + Config: Config{
38 + Mode: "metadata",
39 + Timeout: tc.timeout,
40 + },
41 + }
42 +
43 + err := s.init(context.Background())
44 + if tc.wantErrContains != "" {
45 + require.Error(t, err)
46 + assert.ErrorContains(t, err, tc.wantErrContains)
47 + return
48 + }
49 +
50 + require.NoError(t, err)
51 + assert.Equal(t, tc.wantTimeout, s.runtime.apiClient.Timeout)
52 + assert.Equal(t, tc.wantTimeout, s.runtime.metadataClient.Timeout)
53 + assert.Equal(t, confopt.Duration(tc.wantTimeout), s.Config.Timeout)
54 + })
55 + }
56 +}
57 +
58 +func TestInitBuildsStoreScopedRuntime(t *testing.T) {
59 + creator := New()
60 +
61 + first, ok := creator.Create().(*store)
62 + require.True(t, ok)
63 + second, ok := creator.Create().(*store)
64 + require.True(t, ok)
65 +
66 + assert.Equal(t, defaultTimeout, first.Config.Timeout)
67 + first.Mode = "metadata"
68 + second.Mode = "metadata"
69 +
70 + require.NoError(t, first.init(context.Background()))
71 + require.NoError(t, second.init(context.Background()))
72 +
73 + require.NotNil(t, first.runtime)
74 + require.NotNil(t, second.runtime)
75 + assert.NotSame(t, first.runtime, second.runtime)
76 + assert.NotSame(t, first.runtime.apiClient, second.runtime.apiClient)
77 + assert.NotSame(t, first.runtime.metadataClient, second.runtime.metadataClient)
78 + assert.Equal(t, defaultTimeout.Duration(), first.runtime.apiClient.Timeout)
79 + assert.Equal(t, defaultTimeout.Duration(), first.runtime.metadataClient.Timeout)
80 +}
src/go/plugin/agent/secrets/secretstore/backends/gcp/metadata.yaml
+4
@@ -64,6 +64,10 @@ setup:
64 description: 'Absolute path to a service account JSON file. Required when `mode` is `service_account_file`. The file contains a private key and should be readable only by the `netdata` user or another tightly scoped owner.'
65 default_value: ''
66 required: true
67 + - name: 'timeout'
68 + description: 'Timeout in seconds for HTTP requests made by this secretstore backend.'
69 + default_value: 3
70 + required: false
71 examples:
72 folding:
73 title: 'Example configuration'
src/go/plugin/agent/secrets/secretstore/backends/gcp/provider.go
+14 -17
@@ -9,8 +9,8 @@ import (
9 "regexp"
10 "time"
11
12 + "github.com/netdata/netdata/go/plugins/pkg/confopt"
13 "github.com/netdata/netdata/go/plugins/plugin/agent/secrets/secretstore"
13 - "github.com/netdata/netdata/go/plugins/plugin/agent/secrets/secretstore/internal/httpx"
14 )
15
16 var (
@@ -20,53 +20,50 @@ var (
20 reGCPSafeName = regexp.MustCompile(`^[a-zA-Z0-9_-]+$`)
21 )
22
23 +var defaultTimeout = confopt.Duration(3 * time.Second)
24 +
25 type Config struct {
26 Mode string `json:"mode" yaml:"mode"`
27 ModeServiceAccountFile *ModeServiceAccountFileConfig `json:"mode_service_account_file,omitempty" yaml:"mode_service_account_file,omitempty"`
28 + Timeout confopt.Duration `json:"timeout,omitempty" yaml:"timeout,omitempty"`
29 }
30
31 type ModeServiceAccountFileConfig struct {
32 Path string `json:"path" yaml:"path"`
33 }
34
32 -type provider struct {
35 +type runtime struct {
36 apiClient *http.Client
37 metadataClient *http.Client
35 - secretEndpoint string
36 - now func() time.Time
38 }
39
40 type store struct {
41 Config `yaml:",inline" json:""`
41 - provider *provider
42 + runtime *runtime
43 published *publishedStore
44 }
45
46 type publishedStore struct {
46 - provider *provider
47 + runtime *runtime
48 mode string
49 serviceAccountFilePath string
50 }
51
52 func New() secretstore.Creator {
52 - p := &provider{
53 - apiClient: httpx.APIClient(10 * time.Second),
54 - metadataClient: httpx.NoProxyClient(2 * time.Second),
55 - now: time.Now,
56 - }
57 -
53 return secretstore.Creator{
54 Kind: secretstore.KindGCPSM,
55 DisplayName: "Google Secret Manager",
56 Schema: configSchema,
62 - Create: p.create,
57 + Create: func() secretstore.Store {
58 + return &store{
59 + Config: Config{
60 + Timeout: defaultTimeout,
61 + },
62 + }
63 + },
64 }
65 }
66
66 -func (p *provider) create() secretstore.Store {
67 - return &store{provider: p}
68 -}
69 -
67 func (s *store) Configuration() any { return &s.Config }
68
69 func (s *store) Init(ctx context.Context) error { return s.init(ctx) }
src/go/plugin/agent/secrets/secretstore/backends/gcp/resolve.go
+6 -9
@@ -18,6 +18,7 @@ import (
18 "net/url"
19 "os"
20 "strings"
21 + "time"
22
23 "github.com/netdata/netdata/go/plugins/logger"
24 "github.com/netdata/netdata/go/plugins/plugin/agent/secrets/secretstore"
@@ -48,16 +49,12 @@ func (s *publishedStore) resolve(ctx context.Context, req secretstore.ResolveReq
49 return "", fmt.Errorf("resolving secret '%s': store '%s': %w", req.Original, req.StoreKey, err)
50 }
51
51 - baseURL := s.provider.secretEndpoint
52 - if baseURL == "" {
53 - baseURL = "https://secretmanager.googleapis.com"
54 - }
55 - httpReq, err := http.NewRequestWithContext(ctx, http.MethodGet, fmt.Sprintf("%s/v1/projects/%s/secrets/%s/versions/%s:access", baseURL, project, secretName, version), nil)
52 + httpReq, err := http.NewRequestWithContext(ctx, http.MethodGet, fmt.Sprintf("https://secretmanager.googleapis.com/v1/projects/%s/secrets/%s/versions/%s:access", project, secretName, version), nil)
53 if err != nil {
54 return "", fmt.Errorf("resolving secret '%s': store '%s': creating request: %w", req.Original, req.StoreKey, err)
55 }
56 httpReq.Header.Set("Authorization", "Bearer "+token)
60 - resp, err := s.provider.apiClient.Do(httpReq)
57 + resp, err := s.runtime.apiClient.Do(httpReq)
58 if err != nil {
59 return "", fmt.Errorf("resolving secret '%s': store '%s': request failed: %w", req.Original, req.StoreKey, err)
60 }
@@ -124,7 +121,7 @@ func (s *publishedStore) metadataToken(ctx context.Context) (string, error) {
121 return "", fmt.Errorf("creating metadata token request: %w", err)
122 }
123 req.Header.Set("Metadata-Flavor", "Google")
127 - resp, err := s.provider.metadataClient.Do(req)
124 + resp, err := s.runtime.metadataClient.Do(req)
125 if err != nil {
126 return "", fmt.Errorf("metadata token request failed: %w", err)
127 }
@@ -164,7 +161,7 @@ func (s *publishedStore) serviceAccountToken(ctx context.Context, credFile strin
161 if sa.ClientEmail == "" || sa.PrivateKey == "" || sa.TokenURI == "" {
162 return "", fmt.Errorf("service account JSON missing required fields (client_email, private_key, token_uri)")
163 }
167 - now := s.provider.now().Unix()
164 + now := time.Now().Unix()
165 signedJWT, err := createSignedJWT(sa.ClientEmail, sa.TokenURI, sa.PrivateKey, now)
166 if err != nil {
167 return "", err
@@ -178,7 +175,7 @@ func (s *publishedStore) serviceAccountToken(ctx context.Context, credFile strin
175 return "", fmt.Errorf("creating token exchange request: %w", err)
176 }
177 httpReq.Header.Set("Content-Type", "application/x-www-form-urlencoded")
181 - resp, err := s.provider.apiClient.Do(httpReq)
178 + resp, err := s.runtime.apiClient.Do(httpReq)
179 if err != nil {
180 return "", fmt.Errorf("token exchange request failed: %w", err)
181 }
src/go/plugin/agent/secrets/secretstore/backends/gcp/resolve_test.go
+1 -1
@@ -24,7 +24,7 @@ func (f roundTripFunc) RoundTrip(req *http.Request) (*http.Response, error) {
24
25 func TestPublishedStoreResolve_LogsDetailedResolution(t *testing.T) {
26 s := &publishedStore{
27 - provider: &provider{
27 + runtime: &runtime{
28 apiClient: &http.Client{Transport: roundTripFunc(func(req *http.Request) (*http.Response, error) {
29 assert.Equal(t, "/v1/projects/my-project/secrets/my-secret/versions/latest:access", req.URL.Path)
30 return &http.Response{
src/go/plugin/agent/secrets/secretstore/backends/vault/config_schema.json
+7
@@ -29,6 +29,13 @@
29 "description": "Disable TLS certificate verification for Vault requests.",
30 "type": "boolean",
31 "default": false
32 + },
33 + "timeout": {
34 + "title": "Timeout",
35 + "description": "Timeout in seconds for HTTP requests made by this secretstore backend.",
36 + "type": "number",
37 + "minimum": 0,
38 + "default": 3
39 }
40 },
41 "required": [
src/go/plugin/agent/secrets/secretstore/backends/vault/init.go
+12 -2
@@ -8,12 +8,22 @@ import (
8 "strings"
9
10 "github.com/netdata/netdata/go/plugins/plugin/agent/secrets/secretstore"
11 + "github.com/netdata/netdata/go/plugins/plugin/agent/secrets/secretstore/internal/httpx"
12 )
13
14 func (s *store) init(_ context.Context) error {
14 - published := &publishedStore{
15 - provider: s.provider,
15 + switch {
16 + case s.Config.Timeout.Duration() < 0:
17 + return fmt.Errorf("timeout cannot be negative")
18 + case s.Config.Timeout.Duration() == 0:
19 + s.Config.Timeout = defaultTimeout
20 }
21 + s.runtime = &runtime{
22 + httpClient: httpx.VaultClient(s.Config.Timeout.Duration()),
23 + httpClientInsecure: httpx.VaultInsecureClient(s.Config.Timeout.Duration()),
24 + }
25 +
26 + published := &publishedStore{runtime: s.runtime}
27
28 switch strings.TrimSpace(s.Config.Mode) {
29 case "token":
src/go/plugin/agent/secrets/secretstore/backends/vault/init_test.go new
+88
@@ -0,0 +1,88 @@
1 +// SPDX-License-Identifier: GPL-3.0-or-later
2 +
3 +package vault
4 +
5 +import (
6 + "context"
7 + "testing"
8 + "time"
9 +
10 + "github.com/netdata/netdata/go/plugins/pkg/confopt"
11 + "github.com/stretchr/testify/assert"
12 + "github.com/stretchr/testify/require"
13 +)
14 +
15 +func TestStoreInitTimeout(t *testing.T) {
16 + tests := map[string]struct {
17 + timeout confopt.Duration
18 + wantTimeout time.Duration
19 + wantErrContains string
20 + }{
21 + "default timeout": {
22 + wantTimeout: defaultTimeout.Duration(),
23 + },
24 + "configured timeout": {
25 + timeout: confopt.Duration(7 * time.Second),
26 + wantTimeout: 7 * time.Second,
27 + },
28 + "negative timeout": {
29 + timeout: confopt.Duration(-time.Second),
30 + wantErrContains: "timeout cannot be negative",
31 + },
32 + }
33 +
34 + for name, tc := range tests {
35 + t.Run(name, func(t *testing.T) {
36 + s := &store{
37 + Config: Config{
38 + Mode: "token",
39 + ModeToken: &ModeTokenConfig{
40 + Token: "vault-token",
41 + },
42 + Addr: "https://vault.example",
43 + Timeout: tc.timeout,
44 + },
45 + }
46 +
47 + err := s.init(context.Background())
48 + if tc.wantErrContains != "" {
49 + require.Error(t, err)
50 + assert.ErrorContains(t, err, tc.wantErrContains)
51 + return
52 + }
53 +
54 + require.NoError(t, err)
55 + assert.Equal(t, tc.wantTimeout, s.runtime.httpClient.Timeout)
56 + assert.Equal(t, tc.wantTimeout, s.runtime.httpClientInsecure.Timeout)
57 + assert.Equal(t, confopt.Duration(tc.wantTimeout), s.Config.Timeout)
58 + })
59 + }
60 +}
61 +
62 +func TestInitBuildsStoreScopedRuntime(t *testing.T) {
63 + creator := New()
64 +
65 + first, ok := creator.Create().(*store)
66 + require.True(t, ok)
67 + second, ok := creator.Create().(*store)
68 + require.True(t, ok)
69 +
70 + assert.Equal(t, defaultTimeout, first.Config.Timeout)
71 + first.Mode = "token"
72 + first.ModeToken = &ModeTokenConfig{Token: "vault-token"}
73 + first.Addr = "https://vault.example"
74 + second.Mode = "token"
75 + second.ModeToken = &ModeTokenConfig{Token: "vault-token"}
76 + second.Addr = "https://vault.example"
77 +
78 + require.NoError(t, first.init(context.Background()))
79 + require.NoError(t, second.init(context.Background()))
80 +
81 + require.NotNil(t, first.runtime)
82 + require.NotNil(t, second.runtime)
83 + assert.NotSame(t, first.runtime, second.runtime)
84 + assert.NotSame(t, first.runtime.httpClient, second.runtime.httpClient)
85 + assert.NotSame(t, first.runtime.httpClientInsecure, second.runtime.httpClientInsecure)
86 + assert.Equal(t, defaultTimeout.Duration(), first.runtime.httpClient.Timeout)
87 + assert.Equal(t, defaultTimeout.Duration(), first.runtime.httpClientInsecure.Timeout)
88 +}
src/go/plugin/agent/secrets/secretstore/backends/vault/metadata.yaml
+4
@@ -72,6 +72,10 @@ setup:
72 required: false
73 detailed_description: |
74 This is insecure. Use it only as a temporary workaround or in a non-production environment.
75 + - name: 'timeout'
76 + description: 'Timeout in seconds for HTTP requests made by this secretstore backend.'
77 + default_value: 3
78 + required: false
79 - name: 'mode_token.token'
80 group: 'Token'
81 description: 'Vault token value. Required when `mode` is `token`.'
src/go/plugin/agent/secrets/secretstore/backends/vault/provider.go
+14 -14
@@ -8,13 +8,15 @@ import (
8 "net/http"
9 "time"
10
11 + "github.com/netdata/netdata/go/plugins/pkg/confopt"
12 "github.com/netdata/netdata/go/plugins/plugin/agent/secrets/secretstore"
12 - "github.com/netdata/netdata/go/plugins/plugin/agent/secrets/secretstore/internal/httpx"
13 )
14
15 //go:embed config_schema.json
16 var configSchema string
17
18 +var defaultTimeout = confopt.Duration(3 * time.Second)
19 +
20 type Config struct {
21 Mode string `json:"mode" yaml:"mode"`
22 ModeToken *ModeTokenConfig `json:"mode_token,omitempty" yaml:"mode_token,omitempty"`
@@ -22,6 +24,7 @@ type Config struct {
24 Addr string `json:"addr" yaml:"addr"`
25 Namespace string `json:"namespace,omitempty" yaml:"namespace,omitempty"`
26 TLSSkipVerify bool `json:"tls_skip_verify,omitempty" yaml:"tls_skip_verify,omitempty"`
27 + Timeout confopt.Duration `json:"timeout,omitempty" yaml:"timeout,omitempty"`
28 }
29
30 type ModeTokenConfig struct {
@@ -32,19 +35,19 @@ type ModeTokenFileConfig struct {
35 Path string `json:"path" yaml:"path"`
36 }
37
35 -type provider struct {
38 +type runtime struct {
39 httpClient *http.Client
40 httpClientInsecure *http.Client
41 }
42
43 type store struct {
44 Config `yaml:",inline" json:""`
42 - provider *provider
45 + runtime *runtime
46 published *publishedStore
47 }
48
49 type publishedStore struct {
47 - provider *provider
50 + runtime *runtime
51 mode string
52 tokenValue string
53 tokenFilePath string
@@ -54,23 +57,20 @@ type publishedStore struct {
57 }
58
59 func New() secretstore.Creator {
57 - p := &provider{
58 - httpClient: httpx.VaultClient(10 * time.Second),
59 - httpClientInsecure: httpx.VaultInsecureClient(10 * time.Second),
60 - }
61 -
60 return secretstore.Creator{
61 Kind: secretstore.KindVault,
62 DisplayName: "Vault",
63 Schema: configSchema,
66 - Create: p.create,
64 + Create: func() secretstore.Store {
65 + return &store{
66 + Config: Config{
67 + Timeout: defaultTimeout,
68 + },
69 + }
70 + },
71 }
72 }
73
70 -func (p *provider) create() secretstore.Store {
71 - return &store{provider: p}
72 -}
73 -
74 func (s *store) Configuration() any { return &s.Config }
75
76 func (s *store) Init(ctx context.Context) error { return s.init(ctx) }
src/go/plugin/agent/secrets/secretstore/backends/vault/resolve.go
+2 -2
@@ -51,9 +51,9 @@ func (s *publishedStore) resolve(ctx context.Context, req secretstore.ResolveReq
51 httpReq.Header.Set("X-Vault-Namespace", ns)
52 }
53
54 - client := s.provider.httpClient
54 + client := s.runtime.httpClient
55 if s.skipVerify() {
56 - client = s.provider.httpClientInsecure
56 + client = s.runtime.httpClientInsecure
57 }
58
59 resp, err := client.Do(httpReq)
src/go/plugin/agent/secrets/secretstore/backends/vault/resolve_test.go
+1 -1
@@ -81,7 +81,7 @@ func TestParseResponse(t *testing.T) {
81
82 func TestPublishedStoreResolve_LogsDetailedResolution(t *testing.T) {
83 s := &publishedStore{
84 - provider: &provider{
84 + runtime: &runtime{
85 httpClient: &http.Client{Transport: roundTripFunc(func(req *http.Request) (*http.Response, error) {
86 return &http.Response{
87 StatusCode: http.StatusOK,