master
go 182 lines 6.15 KB
Raw
1 // SPDX-License-Identifier: GPL-3.0-or-later
2
3 package secretsctl
4
5 import (
6 "bytes"
7 "context"
8 "testing"
9
10 "github.com/netdata/netdata/go/plugins/plugin/agent/secrets/secretstore"
11 "github.com/netdata/netdata/go/plugins/plugin/agent/secrets/secretstore/backends"
12 "github.com/netdata/netdata/go/plugins/plugin/framework/confgroup"
13 "github.com/netdata/netdata/go/plugins/plugin/framework/dyncfg"
14 "github.com/netdata/netdata/go/plugins/plugin/framework/functions"
15 "github.com/stretchr/testify/assert"
16 "github.com/stretchr/testify/require"
17 )
18
19 func TestControllerRememberDiscoveredConfig_InvalidDoesNotEnterCaches(t *testing.T) {
20 ctl, _, _ := newVaultControllerTestSubject()
21
22 raw := newSecretStoreConfigWithSource(t, secretstore.KindVault, "vault_prod", map[string]any{}, "/etc/netdata/secretstores.yaml", confgroup.TypeUser)
23
24 entry, changed, err := ctl.RememberDiscoveredConfig(raw)
25 require.Error(t, err)
26 assert.False(t, changed)
27 assert.Equal(t, Entry{}, entry)
28 assert.Zero(t, ctl.seen.Count())
29 assert.Zero(t, ctl.exposed.Count())
30 }
31
32 func TestControllerRememberDiscoveredConfig_PreservesUnknownFields(t *testing.T) {
33 ctl, _, _ := newVaultControllerTestSubject()
34
35 cfg := vaultModeTokenConfig()
36 cfg["ui_note"] = "kept"
37 cfg["mode_token"].(map[string]any)["extra"] = "kept"
38
39 raw := newSecretStoreConfigWithSource(t, secretstore.KindVault, "vault_prod", cfg, "/etc/netdata/secretstores.yaml", confgroup.TypeUser)
40 entry, changed, err := ctl.RememberDiscoveredConfig(raw)
41 require.NoError(t, err)
42 require.True(t, changed)
43
44 assert.Equal(t, "kept", entry.Cfg["ui_note"])
45
46 modeToken := entry.Cfg["mode_token"].(map[string]any)
47 assert.Equal(t, "kept", modeToken["extra"])
48 }
49
50 func TestControllerSeqExec_FileDefinedConfigBecomesDyncfgOverride(t *testing.T) {
51 ctl, out, _ := newVaultControllerTestSubject()
52 key := secretstore.StoreKey(secretstore.KindVault, "vault_prod")
53
54 fileCfg := newSecretStoreConfigWithSource(t, secretstore.KindVault, "vault_prod", vaultModeTokenConfig(), "file=/etc/netdata/go.d/ss/vault.conf", confgroup.TypeUser)
55 require.NoError(t, ctl.Service().Add(context.Background(), fileCfg))
56 ctl.seen.Add(fileCfg)
57 ctl.exposed.Add(&dyncfg.Entry[secretstore.Config]{
58 Cfg: fileCfg,
59 Status: dyncfg.StatusRunning,
60 })
61
62 updateFn := dyncfg.NewFunction(functions.Function{
63 UID: "ss-file-update",
64 ContentType: "application/json",
65 Payload: mustJSON(t, vaultModeTokenFileConfig()),
66 Args: []string{
67 ctl.configID(key),
68 string(dyncfg.CommandUpdate),
69 },
70 })
71 ctl.SeqExec(updateFn)
72
73 var updateResp map[string]any
74 mustDecodeFunctionPayload(t, out.String(), "ss-file-update", &updateResp)
75 assert.Equal(t, float64(200), updateResp["status"])
76
77 entry, ok := ctl.Lookup(key)
78 require.True(t, ok)
79 assert.Equal(t, dyncfg.StatusRunning, entry.Status)
80 assert.Equal(t, confgroup.TypeDyncfg, entry.Cfg.SourceType())
81 assert.Equal(t, confgroup.TypeDyncfg, entry.Cfg.Source())
82 assert.Equal(t, "token_file", entry.Cfg["mode"])
83
84 removeFn := dyncfg.NewFunction(functions.Function{
85 UID: "ss-file-remove",
86 Args: []string{ctl.configID(key), string(dyncfg.CommandRemove)},
87 })
88 ctl.SeqExec(removeFn)
89
90 var removeResp map[string]any
91 mustDecodeFunctionPayload(t, out.String(), "ss-file-remove", &removeResp)
92 assert.Equal(t, float64(200), removeResp["status"])
93
94 _, ok = ctl.Lookup(key)
95 assert.False(t, ok)
96
97 seenUser, ok := ctl.seen.LookupByUID(fileCfg.UID())
98 require.True(t, ok)
99 assert.Equal(t, fileCfg.UID(), seenUser.UID())
100 assert.Equal(t, 1, ctl.seen.Count())
101 assert.Zero(t, ctl.exposed.Count())
102 }
103
104 func TestControllerRemoveDiscoveredConfig_DoesNotRevealLowerPrioritySeenConfig(t *testing.T) {
105 ctl, _, _ := newVaultControllerTestSubject()
106 key := secretstore.StoreKey(secretstore.KindVault, "vault_prod")
107
108 userCfg := newSecretStoreConfigWithSource(t, secretstore.KindVault, "vault_prod", vaultModeTokenConfig(), "/etc/netdata/secretstores.yaml", confgroup.TypeUser)
109 entry, changed, err := ctl.RememberDiscoveredConfig(userCfg)
110 require.NoError(t, err)
111 require.True(t, changed)
112 assert.Equal(t, userCfg.UID(), entry.Cfg.UID())
113
114 dyncfgCfg := newSecretStoreConfigWithSource(t, secretstore.KindVault, "vault_prod", vaultModeTokenFileConfig(), confgroup.TypeDyncfg, confgroup.TypeDyncfg)
115 entry, changed, err = ctl.RememberDiscoveredConfig(dyncfgCfg)
116 require.NoError(t, err)
117 require.True(t, changed)
118 assert.Equal(t, dyncfgCfg.UID(), entry.Cfg.UID())
119
120 removed, ok := ctl.RemoveDiscoveredConfig(dyncfgCfg)
121 require.True(t, ok)
122 assert.Equal(t, dyncfgCfg.UID(), removed.Cfg.UID())
123
124 _, ok = ctl.Lookup(key)
125 assert.False(t, ok)
126
127 seenUser, ok := ctl.seen.LookupByUID(userCfg.UID())
128 require.True(t, ok)
129 assert.Equal(t, userCfg.UID(), seenUser.UID())
130 assert.Equal(t, 1, ctl.seen.Count())
131 assert.Zero(t, ctl.exposed.Count())
132 }
133
134 func TestControllerSeqExec_RemoveFailedUnpublishedStoreDoesNotRestartDependents(t *testing.T) {
135 ctl, out, seams := newVaultControllerTestSubject()
136 key := secretstore.StoreKey(secretstore.KindVault, "vault_prod")
137
138 raw := newSecretStoreConfigWithSource(t, secretstore.KindVault, "vault_prod", map[string]any{"mode": "token"}, confgroup.TypeDyncfg, confgroup.TypeDyncfg)
139 ctl.seen.Add(raw)
140 ctl.exposed.Add(&dyncfg.Entry[secretstore.Config]{
141 Cfg: raw,
142 Status: dyncfg.StatusFailed,
143 })
144
145 removeFn := dyncfg.NewFunction(functions.Function{
146 UID: "ss-remove-no-restart",
147 Args: []string{ctl.configID(key), string(dyncfg.CommandRemove)},
148 })
149 ctl.SeqExec(removeFn)
150
151 var resp map[string]any
152 mustDecodeFunctionPayload(t, out.String(), "ss-remove-no-restart", &resp)
153 assert.Equal(t, float64(200), resp["status"])
154 assert.Equal(t, "", resp["message"])
155 assert.Empty(t, seams.restartCalls)
156 }
157
158 func newVaultControllerTestSubject() (*Controller, *bytes.Buffer, *controllerSeams) {
159 return newControllerTestSubjectWithOptions(Options{
160 Service: secretstore.NewService(backends.Creators()...),
161 })
162 }
163
164 func vaultModeTokenConfig() map[string]any {
165 return map[string]any{
166 "mode": "token",
167 "mode_token": map[string]any{
168 "token": "vault-token",
169 },
170 "addr": "https://vault.example",
171 }
172 }
173
174 func vaultModeTokenFileConfig() map[string]any {
175 return map[string]any{
176 "mode": "token_file",
177 "mode_token_file": map[string]any{
178 "path": "/var/lib/netdata/vault.token",
179 },
180 "addr": "https://vault.example",
181 }
182 }