Skip to content

Commit c878dd8

Browse files
daemitustobio
andauthored
fix package policy secrets (#821)
* fix package policy secrets * changelog * add tests * lint * lets clean this up a smidge * reflect -> require --------- Co-authored-by: Toby Brain <[email protected]>
1 parent 45613cb commit c878dd8

File tree

6 files changed

+292
-17
lines changed

6 files changed

+292
-17
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
## [Unreleased]
22

3+
- Fix secret handling `elasticstack_fleet_integration_policy` resource. ([#821](https://github.com/elastic/terraform-provider-elasticstack/pull/821))
4+
35
## [0.11.8] - 2024-10-02
46

57
- Add key_id to the `elasticstack_elasticsearch_api_key` resource. ([#789](https://github.com/elastic/terraform-provider-elasticstack/pull/789))

internal/fleet/integration_policy/create.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ func (r *integrationPolicyResource) Create(ctx context.Context, req resource.Cre
3434
return
3535
}
3636

37-
diags = handleReqRespSecrets(ctx, body, policy, resp.Private)
37+
diags = HandleReqRespSecrets(ctx, body, policy, resp.Private)
3838
resp.Diagnostics.Append(diags...)
3939
if resp.Diagnostics.HasError() {
4040
return

internal/fleet/integration_policy/read.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ func (r *integrationPolicyResource) Read(ctx context.Context, req resource.ReadR
3434
return
3535
}
3636

37-
diags = handleRespSecrets(ctx, policy, resp.Private)
37+
diags = HandleRespSecrets(ctx, policy, resp.Private)
3838
resp.Diagnostics.Append(diags...)
3939
if resp.Diagnostics.HasError() {
4040
return

internal/fleet/integration_policy/secrets.go

Lines changed: 35 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -61,21 +61,30 @@ func (s secretStore) Save(ctx context.Context, private privateData) (diags diag.
6161
return private.SetKey(ctx, "secrets", bytes)
6262
}
6363

64-
// handleRespSecrets extracts the wrapped value from each response var, then
64+
// HandleRespSecrets extracts the wrapped value from each response var, then
6565
// replaces any secret refs with the original value from secrets if available.
66-
func handleRespSecrets(ctx context.Context, resp *fleetapi.PackagePolicy, private privateData) (diags diag.Diagnostics) {
66+
func HandleRespSecrets(ctx context.Context, resp *fleetapi.PackagePolicy, private privateData) (diags diag.Diagnostics) {
6767
secrets, nd := newSecretStore(ctx, resp, private)
6868
diags.Append(nd...)
6969
if diags.HasError() {
7070
return
7171
}
7272

73+
handleVar := func(key string, mval map[string]any, vars map[string]any) {
74+
refID := mval["id"].(string)
75+
if original, ok := secrets[refID]; ok {
76+
vars[key] = original
77+
}
78+
}
79+
7380
handleVars := func(vars map[string]any) {
7481
for key, val := range vars {
7582
if mval, ok := val.(map[string]any); ok {
7683
if wrapped, ok := mval["value"]; ok {
7784
vars[key] = wrapped
7885
val = wrapped
86+
} else if v, ok := mval["isSecretRef"]; ok && v == true {
87+
handleVar(key, mval, vars)
7988
} else {
8089
// Don't keep null (missing) values
8190
delete(vars, key)
@@ -84,10 +93,7 @@ func handleRespSecrets(ctx context.Context, resp *fleetapi.PackagePolicy, privat
8493

8594
if mval, ok := val.(map[string]any); ok {
8695
if v, ok := mval["isSecretRef"]; ok && v == true {
87-
refID := mval["id"].(string)
88-
if original, ok := secrets[refID]; ok {
89-
vars[key] = original
90-
}
96+
handleVar(key, mval, vars)
9197
}
9298
}
9399
}
@@ -110,34 +116,49 @@ func handleRespSecrets(ctx context.Context, resp *fleetapi.PackagePolicy, privat
110116
return
111117
}
112118

113-
// handleReqRespSecrets extracts the wrapped value from each response var, then
119+
// HandleReqRespSecrets extracts the wrapped value from each response var, then
114120
// maps any secret refs to the original request value.
115-
func handleReqRespSecrets(ctx context.Context, req fleetapi.PackagePolicyRequest, resp *fleetapi.PackagePolicy, private privateData) (diags diag.Diagnostics) {
121+
func HandleReqRespSecrets(ctx context.Context, req fleetapi.PackagePolicyRequest, resp *fleetapi.PackagePolicy, private privateData) (diags diag.Diagnostics) {
116122
secrets, nd := newSecretStore(ctx, resp, private)
117123
diags.Append(nd...)
118124
if diags.HasError() {
119125
return
120126
}
121127

128+
handleVar := func(key string, mval map[string]any, reqVars map[string]any, respVars map[string]any) {
129+
if v, ok := mval["isSecretRef"]; ok && v == true {
130+
original := reqVars[key]
131+
respVars[key] = original
132+
133+
// Is the original also a secret ref?
134+
// This should only show up during importing and pre 0.11.7 migration.
135+
if moriginal, ok := original.(map[string]any); ok {
136+
if v, ok := moriginal["isSecretRef"]; ok && v == true {
137+
return
138+
}
139+
}
140+
141+
refID := mval["id"].(string)
142+
secrets[refID] = original
143+
}
144+
}
145+
122146
handleVars := func(reqVars map[string]any, respVars map[string]any) {
123147
for key, val := range respVars {
124148
if mval, ok := val.(map[string]any); ok {
125149
if wrapped, ok := mval["value"]; ok {
126150
respVars[key] = wrapped
127151
val = wrapped
152+
} else if v, ok := mval["isSecretRef"]; ok && v == true {
153+
handleVar(key, mval, reqVars, respVars)
128154
} else {
129155
// Don't keep null (missing) values
130156
delete(respVars, key)
131157
continue
132158
}
133159

134160
if mval, ok := val.(map[string]any); ok {
135-
if v, ok := mval["isSecretRef"]; ok && v == true {
136-
refID := mval["id"].(string)
137-
original := reqVars[key]
138-
secrets[refID] = original
139-
respVars[key] = original
140-
}
161+
handleVar(key, mval, reqVars, respVars)
141162
}
142163
}
143164
}
Lines changed: 252 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,252 @@
1+
package integration_policy_test
2+
3+
import (
4+
"context"
5+
"maps"
6+
"testing"
7+
8+
fleetapi "github.com/elastic/terraform-provider-elasticstack/generated/fleet"
9+
"github.com/elastic/terraform-provider-elasticstack/internal/fleet/integration_policy"
10+
"github.com/elastic/terraform-provider-elasticstack/internal/utils"
11+
"github.com/hashicorp/terraform-plugin-framework/diag"
12+
"github.com/stretchr/testify/require"
13+
)
14+
15+
type privateData map[string]string
16+
17+
func (p *privateData) GetKey(ctx context.Context, key string) ([]byte, diag.Diagnostics) {
18+
if val, ok := (*p)[key]; ok {
19+
return []byte(val), nil
20+
} else {
21+
return nil, nil
22+
}
23+
}
24+
25+
func (p *privateData) SetKey(ctx context.Context, key string, value []byte) diag.Diagnostics {
26+
(*p)[key] = string(value)
27+
return nil
28+
}
29+
30+
type Map = map[string]any
31+
32+
func TestHandleRespSecrets(t *testing.T) {
33+
t.Parallel()
34+
35+
ctx := context.Background()
36+
private := privateData{"secrets": `{"known-secret":"secret"}`}
37+
38+
secretRefs := &[]struct {
39+
Id *string `json:"id,omitempty"`
40+
}{
41+
{Id: utils.Pointer("known-secret")},
42+
}
43+
44+
tests := []struct {
45+
name string
46+
input Map
47+
want Map
48+
}{
49+
{
50+
name: "converts plain",
51+
input: Map{"k": "v"},
52+
want: Map{"k": "v"},
53+
},
54+
{
55+
name: "converts wrapped",
56+
input: Map{"k": Map{"type": "string", "value": "v"}},
57+
want: Map{"k": "v"},
58+
},
59+
{
60+
name: "converts wrapped nil",
61+
input: Map{"k": Map{"type": "string"}},
62+
want: Map{},
63+
},
64+
{
65+
name: "converts secret",
66+
input: Map{"k": Map{"isSecretRef": true, "id": "known-secret"}},
67+
want: Map{"k": "secret"},
68+
},
69+
{
70+
name: "converts wrapped secret",
71+
input: Map{"k": Map{"type": "password", "value": Map{"isSecretRef": true, "id": "known-secret"}}},
72+
want: Map{"k": "secret"},
73+
},
74+
{
75+
name: "converts unknown secret",
76+
input: Map{"k": Map{"isSecretRef": true, "id": "unknown-secret"}},
77+
want: Map{"k": Map{"isSecretRef": true, "id": "unknown-secret"}},
78+
},
79+
{
80+
name: "converts wrapped unknown secret",
81+
input: Map{"k": Map{"type": "password", "value": Map{"isSecretRef": true, "id": "unknown-secret"}}},
82+
want: Map{"k": Map{"isSecretRef": true, "id": "unknown-secret"}},
83+
},
84+
}
85+
86+
for _, tt := range tests {
87+
t.Run(tt.name, func(t *testing.T) {
88+
resp := fleetapi.PackagePolicy{
89+
SecretReferences: secretRefs,
90+
Inputs: map[string]fleetapi.PackagePolicyInput{
91+
"input1": {
92+
Streams: &Map{"stream1": Map{"vars": maps.Clone(tt.input)}},
93+
Vars: utils.Pointer(maps.Clone(tt.input)),
94+
},
95+
},
96+
Vars: utils.Pointer(maps.Clone(tt.input)),
97+
}
98+
wants := fleetapi.PackagePolicy{
99+
Inputs: map[string]fleetapi.PackagePolicyInput{
100+
"input1": {
101+
Streams: &Map{"stream1": Map{"vars": tt.want}},
102+
Vars: &tt.want,
103+
},
104+
},
105+
Vars: &tt.want,
106+
}
107+
108+
diags := integration_policy.HandleRespSecrets(ctx, &resp, &private)
109+
require.Empty(t, diags)
110+
// Policy vars
111+
got := *resp.Vars
112+
want := *wants.Vars
113+
require.Equal(t, want, got)
114+
115+
// Input vars
116+
got = *resp.Inputs["input1"].Vars
117+
want = *wants.Inputs["input1"].Vars
118+
require.Equal(t, want, got)
119+
120+
// Stream vars
121+
got = (*resp.Inputs["input1"].Streams)["stream1"].(Map)["vars"].(Map)
122+
want = (*wants.Inputs["input1"].Streams)["stream1"].(Map)["vars"].(Map)
123+
require.Equal(t, want, got)
124+
125+
// privateData
126+
privateWants := privateData{"secrets": `{"known-secret":"secret"}`}
127+
require.Equal(t, privateWants, private)
128+
})
129+
}
130+
}
131+
132+
func TestHandleReqRespSecrets(t *testing.T) {
133+
t.Parallel()
134+
135+
ctx := context.Background()
136+
137+
secretRefs := &[]struct {
138+
Id *string `json:"id,omitempty"`
139+
}{
140+
{Id: utils.Pointer("known-secret")},
141+
}
142+
143+
tests := []struct {
144+
name string
145+
reqInput Map
146+
respInput Map
147+
want Map
148+
}{
149+
{
150+
name: "converts plain",
151+
reqInput: Map{"k": "v"},
152+
respInput: Map{"k": "v"},
153+
want: Map{"k": "v"},
154+
},
155+
{
156+
name: "converts wrapped",
157+
reqInput: Map{"k": "v"},
158+
respInput: Map{"k": Map{"type": "string", "value": "v"}},
159+
want: Map{"k": "v"},
160+
},
161+
{
162+
name: "converts wrapped nil",
163+
reqInput: Map{"k": nil},
164+
respInput: Map{"k": Map{"type": "string"}},
165+
want: Map{},
166+
},
167+
{
168+
name: "converts secret",
169+
reqInput: Map{"k": "secret"},
170+
respInput: Map{"k": Map{"isSecretRef": true, "id": "known-secret"}},
171+
want: Map{"k": "secret"},
172+
},
173+
{
174+
name: "converts wrapped secret",
175+
reqInput: Map{"k": "secret"},
176+
respInput: Map{"k": Map{"type": "password", "value": Map{"isSecretRef": true, "id": "known-secret"}}},
177+
want: Map{"k": "secret"},
178+
},
179+
{
180+
name: "converts unknown secret",
181+
reqInput: Map{"k": Map{"isSecretRef": true, "id": "unknown-secret"}},
182+
respInput: Map{"k": Map{"isSecretRef": true, "id": "unknown-secret"}},
183+
want: Map{"k": Map{"isSecretRef": true, "id": "unknown-secret"}},
184+
},
185+
{
186+
name: "converts wrapped unknown secret",
187+
reqInput: Map{"k": Map{"isSecretRef": true, "id": "unknown-secret"}},
188+
respInput: Map{"k": Map{"type": "password", "value": Map{"isSecretRef": true, "id": "unknown-secret"}}},
189+
want: Map{"k": Map{"isSecretRef": true, "id": "unknown-secret"}},
190+
},
191+
}
192+
193+
for _, tt := range tests {
194+
t.Run(tt.name, func(t *testing.T) {
195+
req := fleetapi.PackagePolicyRequest{
196+
Inputs: &map[string]fleetapi.PackagePolicyRequestInput{
197+
"input1": {
198+
Streams: &map[string]fleetapi.PackagePolicyRequestInputStream{"stream1": {Vars: utils.Pointer(maps.Clone(tt.reqInput))}},
199+
Vars: utils.Pointer(maps.Clone(tt.reqInput)),
200+
},
201+
},
202+
Vars: utils.Pointer(maps.Clone(tt.reqInput)),
203+
}
204+
resp := fleetapi.PackagePolicy{
205+
SecretReferences: secretRefs,
206+
Inputs: map[string]fleetapi.PackagePolicyInput{
207+
"input1": {
208+
Streams: &Map{"stream1": Map{"vars": maps.Clone(tt.respInput)}},
209+
Vars: utils.Pointer(maps.Clone(tt.respInput)),
210+
},
211+
},
212+
Vars: utils.Pointer(maps.Clone(tt.respInput)),
213+
}
214+
wants := fleetapi.PackagePolicy{
215+
Inputs: map[string]fleetapi.PackagePolicyInput{
216+
"input1": {
217+
Streams: &Map{"stream1": Map{"vars": tt.want}},
218+
Vars: &tt.want,
219+
},
220+
},
221+
Vars: &tt.want,
222+
}
223+
224+
private := privateData{}
225+
diags := integration_policy.HandleReqRespSecrets(ctx, req, &resp, &private)
226+
require.Empty(t, diags)
227+
228+
// Policy vars
229+
got := *resp.Vars
230+
want := *wants.Vars
231+
require.Equal(t, want, got)
232+
233+
// Input vars
234+
got = *resp.Inputs["input1"].Vars
235+
want = *wants.Inputs["input1"].Vars
236+
require.Equal(t, want, got)
237+
238+
// Stream vars
239+
got = (*resp.Inputs["input1"].Streams)["stream1"].(Map)["vars"].(Map)
240+
want = (*wants.Inputs["input1"].Streams)["stream1"].(Map)["vars"].(Map)
241+
require.Equal(t, want, got)
242+
243+
if v, ok := (*req.Vars)["k"]; ok && v == "secret" {
244+
privateWants := privateData{"secrets": `{"known-secret":"secret"}`}
245+
require.Equal(t, privateWants, private)
246+
} else {
247+
privateWants := privateData{"secrets": `{}`}
248+
require.Equal(t, privateWants, private)
249+
}
250+
})
251+
}
252+
}

internal/fleet/integration_policy/update.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ func (r *integrationPolicyResource) Update(ctx context.Context, req resource.Upd
3535
return
3636
}
3737

38-
diags = handleReqRespSecrets(ctx, body, policy, resp.Private)
38+
diags = HandleReqRespSecrets(ctx, body, policy, resp.Private)
3939
resp.Diagnostics.Append(diags...)
4040
if resp.Diagnostics.HasError() {
4141
return

0 commit comments

Comments
 (0)