Skip to content

Commit 42c97d4

Browse files
committed
x-pack/filebeat/input/cel: accept string values for secret_state
Fleet resolves secrets to their stored string values, so when secret_state is configured with secret: true in an integration package, the agent delivers a YAML text string rather than a parsed map. Change SecretState from map[string]interface{} to a custom type that implements the ucfg Unpacker interface, accepting either a map or a string and parsing the string as YAML. This works around a Fleet bug where type: yaml variables with secret: true are silently corrupted during policy compilation. See elastic/kibana#267859
1 parent 9702d0a commit 42c97d4

5 files changed

Lines changed: 114 additions & 4 deletions

File tree

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
# REQUIRED
2+
# Kind can be one of:
3+
# - breaking-change: a change to previously-documented behavior
4+
# - deprecation: functionality that is being removed in a later release
5+
# - bug-fix: fixes a problem in a previous version
6+
# - enhancement: extends functionality but does not break or fix existing behavior
7+
# - feature: new functionality
8+
# - known-issue: problems that we are aware of in a given version
9+
# - security: impacts on the security of a product or a user’s deployment.
10+
# - upgrade: important information for someone upgrading from a prior version
11+
# - other: does not fit into any of the other categories
12+
kind: bug-fix
13+
14+
# REQUIRED for all kinds
15+
# Change summary; a 80ish characters long description of the change.
16+
summary: Accept string values for secret_state to support Fleet secret resolution.
17+
18+
# REQUIRED for breaking-change, deprecation, known-issue
19+
# Long description; in case the summary is not enough to describe the change
20+
# this field accommodate a description without length limits.
21+
# description:
22+
23+
# REQUIRED for breaking-change, deprecation, known-issue
24+
# impact:
25+
26+
# REQUIRED for breaking-change, deprecation, known-issue
27+
# action:
28+
29+
# REQUIRED for all kinds
30+
# Affected component; usually one of "elastic-agent", "fleet-server", "filebeat", "metricbeat", "auditbeat", "all", etc.
31+
component: filebeat
32+
33+
# AUTOMATED
34+
# OPTIONAL to manually add other PR URLs
35+
# PR URL: A link the PR that added the changeset.
36+
# If not present is automatically filled by the tooling finding the PR where this changelog fragment has been added.
37+
# NOTE: the tooling supports backports, so it's able to fill the original PR number instead of the backport PR number.
38+
# Please provide it if you are adding a fragment for a different PR.
39+
# pr: https://github.com/owner/repo/1234
40+
41+
# AUTOMATED
42+
# OPTIONAL to manually add other issue URLs
43+
# Issue URL; optional; the GitHub issue related to this changeset (either closes or is part of).
44+
# If not present is automatically filled by the tooling with the issue linked to the PR number.
45+
# issue: https://github.com/owner/repo/1234

x-pack/filebeat/input/cel/config.go

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import (
1414
"time"
1515

1616
"gopkg.in/natefinch/lumberjack.v2"
17+
"gopkg.in/yaml.v2"
1718

1819
"github.com/elastic/beats/v7/x-pack/filebeat/input/internal/httplog"
1920
"github.com/elastic/beats/v7/x-pack/filebeat/otel"
@@ -57,7 +58,7 @@ type config struct {
5758
// placed at state.secret before CEL program execution.
5859
// The state.secret key is unconditionally redacted in
5960
// debug logs.
60-
SecretState map[string]interface{} `config:"secret_state"`
61+
SecretState secretState `config:"secret_state"`
6162
// Redact is the debug log state redaction configuration.
6263
Redact *redact `config:"redact"`
6364

@@ -100,6 +101,30 @@ func (c config) GetPackageData(key string) string {
100101
return value
101102
}
102103

104+
// secretState holds secret key-value pairs. It implements
105+
// the ucfg Unpacker interface to accept either a map (from
106+
// direct config) or a string (from Fleet secret resolution,
107+
// which delivers the stored YAML text as a scalar).
108+
//
109+
// The string case is needed because Fleet resolves secrets
110+
// to their stored string values. See
111+
// https://github.com/elastic/kibana/issues/267859
112+
type secretState struct {
113+
m map[string]interface{}
114+
}
115+
116+
func (s *secretState) Unpack(v interface{}) error {
117+
switch v := v.(type) {
118+
case map[string]interface{}:
119+
s.m = v
120+
return nil
121+
case string:
122+
return yaml.Unmarshal([]byte(v), &s.m)
123+
default:
124+
return fmt.Errorf("secret_state: expected string or map, got %T", v)
125+
}
126+
}
127+
103128
type redact struct {
104129
// Fields indicates which fields to apply redaction to prior
105130
// to logging.

x-pack/filebeat/input/cel/config_test.go

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,46 @@ func TestRegexpConfig(t *testing.T) {
5454
}
5555
}
5656

57+
func TestSecretStateUnpack(t *testing.T) {
58+
t.Run("map", func(t *testing.T) {
59+
var s secretState
60+
err := s.Unpack(map[string]interface{}{"api_key": "secret"})
61+
if err != nil {
62+
t.Fatalf("unexpected error: %v", err)
63+
}
64+
if s.m["api_key"] != "secret" {
65+
t.Fatalf("unexpected value: got %v, want %q", s.m["api_key"], "secret")
66+
}
67+
})
68+
69+
t.Run("string", func(t *testing.T) {
70+
var s secretState
71+
err := s.Unpack("api_key: secret")
72+
if err != nil {
73+
t.Fatalf("unexpected error: %v", err)
74+
}
75+
if s.m["api_key"] != "secret" {
76+
t.Fatalf("unexpected value: got %v, want %q", s.m["api_key"], "secret")
77+
}
78+
})
79+
80+
t.Run("invalid_type", func(t *testing.T) {
81+
var s secretState
82+
err := s.Unpack(42)
83+
if err == nil {
84+
t.Fatal("expected error for int value")
85+
}
86+
})
87+
88+
t.Run("invalid_yaml", func(t *testing.T) {
89+
var s secretState
90+
err := s.Unpack(":\ninvalid:\n :\n")
91+
if err == nil {
92+
t.Fatal("expected error for invalid YAML")
93+
}
94+
})
95+
}
96+
5797
func TestSecretStateValidation(t *testing.T) {
5898
base := config{
5999
Interval: time.Minute,

x-pack/filebeat/input/cel/input.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -262,8 +262,8 @@ func (i input) run(env v2.Context, src *source, cursor map[string]interface{}, p
262262
} else {
263263
state = cfg.State
264264
}
265-
if len(cfg.SecretState) > 0 {
266-
state["secret"] = cfg.SecretState
265+
if len(cfg.SecretState.m) > 0 {
266+
state["secret"] = cfg.SecretState.m
267267
}
268268
if cfg.Redact == nil {
269269
cfg.Redact = &redact{}

x-pack/filebeat/input/cel/input_manager.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ func (c config) checkUnsupportedParams(logger *logp.Logger) {
5151
"see documentation for details: https://www.elastic.co/guide/en/beats/filebeat/current/filebeat-input-cel.html#cel-record-coverage")
5252
}
5353
if c.Redact == nil {
54-
if len(c.SecretState) > 0 {
54+
if len(c.SecretState.m) > 0 {
5555
logger.Named("cel").Warn("state.secret is automatically redacted, but 'redact' configuration is recommended if other state fields contain sensitive values: " +
5656
"see documentation for details: https://www.elastic.co/guide/en/beats/filebeat/current/filebeat-input-cel.html#cel-state-redact")
5757
} else {

0 commit comments

Comments
 (0)