Skip to content

Commit fe99b75

Browse files
authored
Accept secret references in both formats: inline and path (#5852)
* Rename function for consistency * Add unit test * Implement fallback for inputs secrets replacement * Better naming * Add comment * Rename for broader use * WIP * Remove unnecessary error returns * Make sure to overwrite replaced inline secret keys * Remove TODO * Add assertions for output secrets * Remove inaccurate comment * Adding CHANGELOG fragment * Update secret unit tests * [Debugging] Don't zero out secret references * Add output secret to references * Undo debugging change
1 parent 31a7560 commit fe99b75

File tree

8 files changed

+752
-76
lines changed

8 files changed

+752
-76
lines changed
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
# Kind can be one of:
2+
# - breaking-change: a change to previously-documented behavior
3+
# - deprecation: functionality that is being removed in a later release
4+
# - bug-fix: fixes a problem in a previous version
5+
# - enhancement: extends functionality but does not break or fix existing behavior
6+
# - feature: new functionality
7+
# - known-issue: problems that we are aware of in a given version
8+
# - security: impacts on the security of a product or a user’s deployment.
9+
# - upgrade: important information for someone upgrading from a prior version
10+
# - other: does not fit into any of the other categories
11+
kind: enhancement
12+
13+
# Change summary; a 80ish characters long description of the change.
14+
summary: Accept secret references in policies in either inline or path formats
15+
16+
# Long description; in case the summary is not enough to describe the change
17+
# this field accommodate a description without length limits.
18+
# NOTE: This field will be rendered only for breaking-change and known-issue kinds at the moment.
19+
description: |
20+
Elastic Agent policies can contain secret references in one of two formats: inline or path.
21+
With the inline format, the reference looks like this: `<path>: $co.elastic.secret{<secret ref>}`.
22+
With the path format, the reference looks like this: `secrets.<path>.id:<secret ref>`.
23+
This change ensures that Fleet Server accepts secret references in policies in either format.
24+
25+
# Affected component; usually one of "elastic-agent", "fleet-server", "filebeat", "metricbeat", "auditbeat", "all", etc.
26+
component: fleet-server
27+
28+
# PR URL; optional; the PR number that added the changeset.
29+
# If not present is automatically filled by the tooling finding the PR where this changelog fragment has been added.
30+
# NOTE: the tooling supports backports, so it's able to fill the original PR number instead of the backport PR number.
31+
# Please provide it if you are adding a fragment for a different PR.
32+
#pr: https://github.com/owner/repo/1234
33+
34+
# Issue URL; optional; the GitHub issue related to this changeset (either closes or is part of).
35+
# If not present is automatically filled by the tooling with the issue linked to the PR number.
36+
#issue: https://github.com/owner/repo/1234

internal/pkg/api/handleCheckin.go

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -879,14 +879,17 @@ func processPolicy(ctx context.Context, zlog zerolog.Logger, bulker bulk.Bulk, a
879879
return nil, ErrNoPolicyOutput
880880
}
881881

882+
// Get secret values so secret references in outputs and inputs can be replaced
883+
// with their corresponding values.
884+
secretValues, err := secret.GetSecretValues(ctx, pp.Policy.Data.SecretReferences, bulker)
885+
if err != nil {
886+
return nil, fmt.Errorf("failed to get secret values: %w", err)
887+
}
888+
882889
data := model.ClonePolicyData(pp.Policy.Data)
883-
for policyName, policyOutput := range data.Outputs {
890+
for _, policyOutput := range data.Outputs {
884891
// NOTE: Not sure if output secret keys collected here include new entries, but they are collected for completeness
885-
ks, err := secret.ProcessOutputSecret(ctx, policyOutput, bulker) // makes a bulk request to get secret values
886-
if err != nil {
887-
return nil, fmt.Errorf("failed to process output secrets %q: %w",
888-
policyName, err)
889-
}
892+
ks := secret.ProcessOutputSecret(policyOutput, secretValues)
890893
pp.SecretKeys = append(pp.SecretKeys, ks...)
891894
}
892895
// Iterate through the policy outputs and prepare them

internal/pkg/policy/parsed_policy.go

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -63,15 +63,19 @@ func NewParsedPolicy(ctx context.Context, bulker bulk.Bulk, p model.Policy) (*Pa
6363
return nil, err
6464
}
6565

66+
// Get secret values so secret references in outputs and inputs can be replaced
67+
// with their corresponding values.
68+
secretValues, err := secret.GetSecretValues(ctx, p.Data.SecretReferences, bulker)
69+
if err != nil {
70+
return nil, fmt.Errorf("failed to get secret values: %w", err)
71+
}
72+
6673
policyOutputs, err := constructPolicyOutputs(p.Data.Outputs, roles)
6774
if err != nil {
6875
return nil, err
6976
}
7077
for name, policyOutput := range p.Data.Outputs {
71-
ks, err := secret.ProcessOutputSecret(ctx, policyOutput, bulker)
72-
if err != nil {
73-
return nil, err
74-
}
78+
ks := secret.ProcessOutputSecret(policyOutput, secretValues)
7579
for _, key := range ks {
7680
secretKeys = append(secretKeys, "outputs."+name+"."+key)
7781
}
@@ -80,12 +84,12 @@ func NewParsedPolicy(ctx context.Context, bulker bulk.Bulk, p model.Policy) (*Pa
8084
if err != nil {
8185
return nil, err
8286
}
83-
policyInputs, keys, err := secret.GetPolicyInputsWithSecrets(ctx, p.Data, bulker)
84-
if err != nil {
85-
return nil, err
86-
}
87+
policyInputs, keys := secret.ProcessInputsSecrets(p.Data, secretValues)
8788
secretKeys = append(secretKeys, keys...)
8889

90+
// Done replacing secrets.
91+
p.Data.SecretReferences = nil
92+
8993
// We are cool and the gang
9094
pp := &ParsedPolicy{
9195
Policy: p,

internal/pkg/policy/parsed_policy_test.go

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import (
1515
"github.com/stretchr/testify/require"
1616

1717
"github.com/elastic/fleet-server/v7/internal/pkg/model"
18+
ftesting "github.com/elastic/fleet-server/v7/internal/pkg/testing"
1819
)
1920

2021
//go:embed testdata/test_policy.json
@@ -29,6 +30,9 @@ var logstashOutputPolicy string
2930
//go:embed testdata/remote_es_policy.json
3031
var testPolicyRemoteES string
3132

33+
//go:embed testdata/policy_with_secrets_mixed.json
34+
var policyWithSecretsMixed string
35+
3236
func TestNewParsedPolicy(t *testing.T) {
3337
// Run two formatting of the same payload to validate that the sha2 remains the same
3438
testcases := []struct {
@@ -102,3 +106,36 @@ func TestNewParsedPolicyRemoteES(t *testing.T) {
102106
// Validate that default was found
103107
require.Equal(t, "remote", pp.Default.Name)
104108
}
109+
110+
// TestParsedPolicyMixedSecretsReplacement tests that secrets specified in a policy
111+
// using either the `secrets.<path-to-key>.<key>.id:<secret ref>` format or the
112+
// `<path>: $co.elastic.secret{<secret ref>}` format are both replaced correctly.
113+
func TestParsedPolicyMixedSecretsReplacement(t *testing.T) {
114+
// Load the model into the policy object
115+
var m model.Policy
116+
var d model.PolicyData
117+
err := json.Unmarshal([]byte(policyWithSecretsMixed), &d)
118+
require.NoError(t, err)
119+
120+
m.Data = &d
121+
122+
bulker := ftesting.NewMockBulk()
123+
pp, err := NewParsedPolicy(context.TODO(), bulker, m)
124+
require.NoError(t, err)
125+
126+
// Validate that secrets were identified
127+
require.Len(t, pp.SecretKeys, 4)
128+
require.Contains(t, pp.SecretKeys, "outputs.fs-output.type")
129+
require.Contains(t, pp.SecretKeys, "outputs.fs-output.ssl.key")
130+
require.Contains(t, pp.SecretKeys, "inputs.0.streams.0.auth.basic.password")
131+
require.Contains(t, pp.SecretKeys, "inputs.0.streams.1.auth.basic.password")
132+
133+
// Validate that secret references were replaced
134+
firstInputStreams := pp.Inputs[0]["streams"].([]any)
135+
firstInputFirstStream := firstInputStreams[0].(map[string]any)
136+
firstInputSecondStream := firstInputStreams[1].(map[string]any)
137+
require.Equal(t, "0Mx2UZoBTAyw4gQKSaao_value", firstInputFirstStream["auth.basic.password"])
138+
require.Equal(t, "0Mx2UZoBTAyw4gQKSaao_value", firstInputSecondStream["auth.basic.password"])
139+
require.Equal(t, "abcdef123_value", pp.Policy.Data.Outputs["fs-output"]["type"])
140+
require.Equal(t, "w8yELZoBTAyw4gQK9KZ7_value", pp.Policy.Data.Outputs["fs-output"]["ssl"].(map[string]interface{})["key"])
141+
}

0 commit comments

Comments
 (0)