Skip to content

Commit 13f0c67

Browse files
authored
feat: solve secrets on config and backends setup data (#93)
1 parent e3aadff commit 13f0c67

16 files changed

+383
-110
lines changed

agent/agent.go

+18-11
Original file line numberDiff line numberDiff line change
@@ -75,25 +75,24 @@ func New(logger *slog.Logger, c config.Config) (Agent, error) {
7575
return nil, err
7676
}
7777

78-
cm := configmgr.New(logger, pm, c.OrbAgent.ConfigManager)
78+
cm := configmgr.New(logger, pm, c.OrbAgent.ConfigManager.Active)
7979

8080
return &orbAgent{
8181
logger: logger, config: c, policyManager: pm, configManager: cm,
8282
secretsManager: sm, groupsInfos: make(map[string]groupInfo),
8383
}, nil
8484
}
8585

86-
func (a *orbAgent) startBackends(agentCtx context.Context) error {
86+
func (a *orbAgent) startBackends(agentCtx context.Context, cfgBackends map[string]any, labels map[string]string) error {
8787
a.logger.Info("registered backends", slog.Any("values", backend.GetList()))
88-
a.logger.Info("requested backends", slog.Any("values", a.config.OrbAgent.Backends))
89-
if len(a.config.OrbAgent.Backends) == 0 {
88+
if len(cfgBackends) == 0 {
9089
return errors.New("no backends specified")
9190
}
92-
a.backends = make(map[string]backend.Backend, len(a.config.OrbAgent.Backends))
91+
a.backends = make(map[string]backend.Backend, len(cfgBackends))
9392
a.backendState = make(map[string]*backend.State)
9493

9594
var commonConfig config.BackendCommons
96-
if v, prs := a.config.OrbAgent.Backends["common"]; prs {
95+
if v, prs := cfgBackends["common"]; prs {
9796
bytes, err := yaml.Marshal(v)
9897
if err != nil {
9998
return err
@@ -106,11 +105,11 @@ func (a *orbAgent) startBackends(agentCtx context.Context) error {
106105
} else {
107106
commonConfig = config.BackendCommons{}
108107
}
109-
commonConfig.Otel.AgentLabels = a.config.OrbAgent.Labels
108+
commonConfig.Otel.AgentLabels = labels
110109
a.backendsCommon = commonConfig
111-
delete(a.config.OrbAgent.Backends, "common")
110+
delete(cfgBackends, "common")
112111

113-
for name, configurationEntry := range a.config.OrbAgent.Backends {
112+
for name, configurationEntry := range cfgBackends {
114113
var cEntity map[string]any
115114
if configurationEntry != nil {
116115
var ok bool
@@ -164,17 +163,25 @@ func (a *orbAgent) Start(ctx context.Context, cancelFunc context.CancelFunc) err
164163
a.rpcFromCancelFunc = cancelAllAsync
165164
a.cancelFunction = cancelFunc
166165
a.logger.Info("agent started", slog.String("version", version.GetBuildVersion()), slog.Any("routine", agentCtx.Value(routineKey)))
166+
a.logger.Info("requested backends", slog.Any("values", a.config.OrbAgent.Backends))
167167

168168
if err := a.secretsManager.Start(ctx); err != nil {
169169
a.logger.Error("error during start secrets manager", slog.Any("error", err))
170170
return err
171171
}
172172

173-
if err := a.startBackends(ctx); err != nil {
173+
var err error
174+
if a.config.OrbAgent.Backends,
175+
a.config.OrbAgent.ConfigManager,
176+
err = a.secretsManager.SolveConfigSecrets(a.config.OrbAgent.Backends, a.config.OrbAgent.ConfigManager); err != nil {
177+
return err
178+
}
179+
180+
if err = a.startBackends(ctx, a.config.OrbAgent.Backends, a.config.OrbAgent.Labels); err != nil {
174181
return err
175182
}
176183

177-
if err := a.configManager.Start(a.config, a.backends); err != nil {
184+
if err = a.configManager.Start(a.config, a.backends); err != nil {
178185
return err
179186
}
180187

agent/agent_prof_test.go

-28
This file was deleted.

agent/config/config_test.go

+164
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
1+
package config_test
2+
3+
import (
4+
"os"
5+
"testing"
6+
7+
"github.com/stretchr/testify/assert"
8+
9+
"github.com/netboxlabs/orb-agent/agent/config"
10+
)
11+
12+
func TestResolveEnv(t *testing.T) {
13+
err := os.Setenv("TEST_VAR", "test_value")
14+
assert.NoError(t, err, "failed to set environment variable")
15+
defer func() {
16+
err := os.Unsetenv("TEST_VAR")
17+
assert.NoError(t, err, "failed to unset environment variable")
18+
}()
19+
20+
tests := []struct {
21+
input string
22+
expected string
23+
hasError bool
24+
}{
25+
{"${TEST_VAR}", "test_value", false},
26+
{"${UNSET_VAR}", "", true},
27+
{"no_env_var", "no_env_var", false},
28+
}
29+
30+
for _, test := range tests {
31+
result, err := config.ResolveEnv(test.input)
32+
if test.hasError {
33+
assert.Error(t, err, "expected error for input %s", test.input)
34+
} else {
35+
assert.NoError(t, err, "unexpected error for input %s", test.input)
36+
}
37+
assert.Equal(t, test.expected, result, "unexpected result for input %s", test.input)
38+
}
39+
}
40+
41+
func TestResolveEnvError(t *testing.T) {
42+
err := os.Unsetenv("UNSET_VAR") // Ensure the variable is not set
43+
assert.NoError(t, err, "failed to unset environment variable")
44+
45+
_, err = config.ResolveEnv("${UNSET_VAR}")
46+
assert.Error(t, err, "expected error for unset environment variable")
47+
}
48+
49+
func TestResolveEnvInMap(t *testing.T) {
50+
err := os.Setenv("TEST_VAR", "test_value")
51+
assert.NoError(t, err, "failed to set environment variable")
52+
defer func() {
53+
err := os.Unsetenv("TEST_VAR")
54+
assert.NoError(t, err, "failed to unset environment variable")
55+
}()
56+
57+
data := map[string]any{
58+
"key1": "${TEST_VAR}",
59+
"key2": "static_value",
60+
"nested": map[string]any{
61+
"key3": "${TEST_VAR}",
62+
},
63+
"list": []any{
64+
"${TEST_VAR}",
65+
},
66+
}
67+
68+
err = config.ResolveEnvInMap(data)
69+
assert.NoError(t, err, "unexpected error")
70+
71+
assert.Equal(t, "test_value", data["key1"], "unexpected value for key1")
72+
assert.Equal(t, "static_value", data["key2"], "unexpected value for key2")
73+
74+
nested, ok := data["nested"].(map[string]any)
75+
assert.True(t, ok, "expected nested to be a map")
76+
assert.Equal(t, "test_value", nested["key3"], "unexpected value for nested.key3")
77+
}
78+
79+
func TestResolveEnvInMapError(t *testing.T) {
80+
err := os.Unsetenv("UNSET_VAR") // Ensure the variable is not set
81+
assert.NoError(t, err, "failed to unset environment variable")
82+
83+
data := map[string]any{
84+
"key1": "${UNSET_VAR}",
85+
}
86+
87+
err = config.ResolveEnvInMap(data)
88+
assert.Error(t, err, "expected error for unset environment variable in map")
89+
90+
// Test with a nested map
91+
data = map[string]any{
92+
"key1": map[string]any{
93+
"nested_key": "${UNSET_VAR}",
94+
},
95+
}
96+
err = config.ResolveEnvInMap(data)
97+
assert.Error(t, err, "expected error for unset environment variable in nested map")
98+
99+
// Test with a nested slice
100+
data = map[string]any{
101+
"key1": []any{"${UNSET_VAR}"},
102+
}
103+
err = config.ResolveEnvInMap(data)
104+
assert.Error(t, err, "expected error for unset environment variable in nested slice")
105+
}
106+
107+
func TestResolveEnvInSlice(t *testing.T) {
108+
err := os.Setenv("TEST_VAR", "test_value")
109+
assert.NoError(t, err, "failed to set environment variable")
110+
defer func() {
111+
err := os.Unsetenv("TEST_VAR")
112+
assert.NoError(t, err, "failed to unset environment variable")
113+
}()
114+
115+
data := []any{
116+
"${TEST_VAR}",
117+
"static_value",
118+
map[string]any{
119+
"key1": "${TEST_VAR}",
120+
},
121+
[]any{"${TEST_VAR}"},
122+
}
123+
124+
err = config.ResolveEnvInSlice(data)
125+
assert.NoError(t, err, "unexpected error")
126+
127+
assert.Equal(t, "test_value", data[0], "unexpected value at index 0")
128+
assert.Equal(t, "static_value", data[1], "unexpected value at index 1")
129+
130+
nestedMap, ok := data[2].(map[string]any)
131+
assert.True(t, ok, "expected index 2 to be a map")
132+
assert.Equal(t, "test_value", nestedMap["key1"], "unexpected value for nestedMap.key1")
133+
134+
nestedSlice, ok := data[3].([]any)
135+
assert.True(t, ok, "expected index 3 to be a slice")
136+
assert.Equal(t, "test_value", nestedSlice[0], "unexpected value for nestedSlice[0]")
137+
}
138+
139+
func TestResolveEnvInSliceError(t *testing.T) {
140+
err := os.Unsetenv("UNSET_VAR") // Ensure the variable is not set
141+
assert.NoError(t, err, "failed to unset environment variable")
142+
143+
data := []any{
144+
"${UNSET_VAR}",
145+
}
146+
147+
err = config.ResolveEnvInSlice(data)
148+
assert.Error(t, err, "expected error for unset environment variable in slice")
149+
150+
// Test with a nested map
151+
data = []any{
152+
map[string]any{
153+
"key1": "${UNSET_VAR}",
154+
},
155+
}
156+
err = config.ResolveEnvInSlice(data)
157+
assert.Error(t, err, "expected error for unset environment variable in nested map")
158+
// Test with a nested slice
159+
data = []any{
160+
[]any{"${UNSET_VAR}"},
161+
}
162+
err = config.ResolveEnvInSlice(data)
163+
assert.Error(t, err, "expected error for unset environment variable in nested slice")
164+
}

agent/config/env.go

+70
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
package config
2+
3+
import (
4+
"errors"
5+
"os"
6+
"strings"
7+
)
8+
9+
// ResolveEnv replaces environment variable placeholders in the format ${VAR} with their actual values.
10+
func ResolveEnv(value string) (string, error) {
11+
// Check if the value starts with "${" and ends with "}"
12+
if strings.HasPrefix(value, "${") && strings.HasSuffix(value, "}") {
13+
// Extract the environment variable name
14+
envVar := value[2 : len(value)-1]
15+
// Get the value of the environment variable
16+
envValue := os.Getenv(envVar)
17+
if envValue != "" {
18+
return envValue, nil
19+
}
20+
return "", errors.New("a provided environment variable is not set")
21+
}
22+
// Return the original value if no substitution occurs
23+
return value, nil
24+
}
25+
26+
// ResolveEnvInMap recursively traverses and resolves env vars in map[string]any structures.
27+
func ResolveEnvInMap(data map[string]any) error {
28+
for key, val := range data {
29+
switch v := val.(type) {
30+
case string:
31+
resolved, err := ResolveEnv(v)
32+
if err != nil {
33+
return err
34+
}
35+
data[key] = resolved
36+
case map[string]any:
37+
if err := ResolveEnvInMap(v); err != nil {
38+
return err
39+
}
40+
case []any:
41+
if err := ResolveEnvInSlice(v); err != nil {
42+
return err
43+
}
44+
}
45+
}
46+
return nil
47+
}
48+
49+
// ResolveEnvInSlice handles []any elements recursively.
50+
func ResolveEnvInSlice(slice []any) error {
51+
for i, val := range slice {
52+
switch v := val.(type) {
53+
case string:
54+
resolved, err := ResolveEnv(v)
55+
if err != nil {
56+
return err
57+
}
58+
slice[i] = resolved
59+
case map[string]any:
60+
if err := ResolveEnvInMap(v); err != nil {
61+
return err
62+
}
63+
case []any:
64+
if err := ResolveEnvInSlice(v); err != nil {
65+
return err
66+
}
67+
}
68+
}
69+
return nil
70+
}

agent/configmgr/git.go

+3-20
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,7 @@ import (
55
"errors"
66
"io"
77
"log/slog"
8-
"os"
98
"slices"
10-
"strings"
119

1210
"github.com/go-co-op/gocron/v2"
1311
gitv5 "github.com/go-git/go-git/v5"
@@ -50,22 +48,6 @@ type (
5048
}
5149
)
5250

53-
func resolveEnv(value string) (string, error) {
54-
// Check if the value starts with ${ and ends with }
55-
if strings.HasPrefix(value, "${") && strings.HasSuffix(value, "}") {
56-
// Extract the environment variable name
57-
envVar := value[2 : len(value)-1]
58-
// Get the value of the environment variable
59-
envValue := os.Getenv(envVar)
60-
if envValue != "" {
61-
return envValue, nil
62-
}
63-
return "", errors.New("a provided environment variable is not set")
64-
}
65-
// Return the original value if no substitution occurs
66-
return value, nil
67-
}
68-
6951
func (gc *gitConfigManager) readPolicies(tree *object.Tree, matchingPolicies []string) (map[policyPath]policyData, error) {
7052
policiesByPath := make(map[policyPath]policyData)
7153
allPolicies := make(map[string]map[string]any)
@@ -355,13 +337,14 @@ func (gc *gitConfigManager) schedule(cfg config.Config, backends map[string]back
355337
func (gc *gitConfigManager) Start(cfg config.Config, backends map[string]backend.Backend) error {
356338
var err error
357339
gc.version = 1
340+
gc.config = cfg.OrbAgent.ConfigManager.Sources.Git
358341

359342
if gc.config.URL == "" {
360343
return errors.New("URL is required for Git Config Manager")
361344
}
362345

363346
if gc.config.Auth == "basic" {
364-
if gc.config.Password, err = resolveEnv(gc.config.Password); err != nil {
347+
if gc.config.Password, err = config.ResolveEnv(gc.config.Password); err != nil {
365348
return err
366349
}
367350
gc.authMethod = &http.BasicAuth{
@@ -370,7 +353,7 @@ func (gc *gitConfigManager) Start(cfg config.Config, backends map[string]backend
370353
}
371354
} else if gc.config.Auth == "ssh" {
372355
if gc.config.PrivateKey != "" {
373-
if gc.config.Password, err = resolveEnv(gc.config.Password); err != nil {
356+
if gc.config.Password, err = config.ResolveEnv(gc.config.Password); err != nil {
374357
return err
375358
}
376359
gc.authMethod, err = ssh.NewPublicKeysFromFile("git", gc.config.PrivateKey, gc.config.Password)

0 commit comments

Comments
 (0)