diff --git a/changelog/fragments/1764357992-Add-environment.yml-file-to-diagnostics.yaml b/changelog/fragments/1764357992-Add-environment.yml-file-to-diagnostics.yaml new file mode 100644 index 00000000000..2ce5117edac --- /dev/null +++ b/changelog/fragments/1764357992-Add-environment.yml-file-to-diagnostics.yaml @@ -0,0 +1,32 @@ +# Kind can be one of: +# - breaking-change: a change to previously-documented behavior +# - deprecation: functionality that is being removed in a later release +# - bug-fix: fixes a problem in a previous version +# - enhancement: extends functionality but does not break or fix existing behavior +# - feature: new functionality +# - known-issue: problems that we are aware of in a given version +# - security: impacts on the security of a product or a user’s deployment. +# - upgrade: important information for someone upgrading from a prior version +# - other: does not fit into any of the other categories +kind: bug-fix + +# Change summary; a 80ish characters long description of the change. +summary: Add environment.yaml file to diagnostics + +# Long description; in case the summary is not enough to describe the change +# this field accommodate a description without length limits. +# NOTE: This field will be rendered only for breaking-change and known-issue kinds at the moment. +#description: + +# Affected component; usually one of "elastic-agent", "fleet-server", "filebeat", "metricbeat", "auditbeat", "all", etc. +component: elastic-agent + +# PR URL; optional; the PR number that added the changeset. +# If not present is automatically filled by the tooling finding the PR where this changelog fragment has been added. +# NOTE: the tooling supports backports, so it's able to fill the original PR number instead of the backport PR number. +# Please provide it if you are adding a fragment for a different PR. +#pr: https://github.com/owner/repo/1234 + +# Issue URL; optional; the GitHub issue related to this changeset (either closes or is part of). +# If not present is automatically filled by the tooling with the issue linked to the PR number. +issue: https://github.com/elastic/elastic-agent/issues/10966 diff --git a/internal/pkg/diagnostics/diagnostics.go b/internal/pkg/diagnostics/diagnostics.go index 8240e274a71..a6435a4ac51 100644 --- a/internal/pkg/diagnostics/diagnostics.go +++ b/internal/pkg/diagnostics/diagnostics.go @@ -95,6 +95,23 @@ func GlobalHooks() Hooks { return fileBytes }, }, + { + Name: "environment", + Filename: "environment.yaml", + Description: "Environment variables", + ContentType: "application/yaml", + Hook: func(_ context.Context) []byte { + redacted, err := redactEnv() + if err != nil { + return []byte(err.Error()) + } + out, err := yaml.Marshal(redacted) + if err != nil { + return []byte(fmt.Sprintf("Unable to marshall env vars into yaml: %v", err)) + } + return out + }, + }, { Name: "goroutine", Filename: "goroutine.pprof.gz", @@ -702,3 +719,17 @@ func addSecretMarkers(cfg *config.Config, secretPaths []string) error { return aggregateError } + +func redactEnv() (map[string]any, error) { + envMap := map[string]any{} + for _, e := range os.Environ() { + pair := strings.SplitN(e, "=", 2) + envMap[pair[0]] = pair[1] + } + var errOut bytes.Buffer + redacted := Redact(envMap, &errOut) + if errOut.Len() > 0 { + return nil, errors.New(errOut.String()) + } + return redacted, nil +} diff --git a/internal/pkg/diagnostics/diagnostics_test.go b/internal/pkg/diagnostics/diagnostics_test.go index 0c590c9f0ed..17377afb6f2 100644 --- a/internal/pkg/diagnostics/diagnostics_test.go +++ b/internal/pkg/diagnostics/diagnostics_test.go @@ -495,6 +495,7 @@ func TestGlobalHooks(t *testing.T) { assert.NoErrorf(t, err, "hook %q validation error: %v", err) case "package version": assert.Equal(t, testPkgVer, string(output), "hook package version does not match") + case "environment": default: ok, err = isPprof(output) assert.Truef(t, ok, "hook %q returned incompatible data: %q", h.Name, hex.EncodeToString(output)) @@ -858,3 +859,51 @@ func TestRedactSSLKeyInInputs(t *testing.T) { require.Contains(t, sliceSSL, "key") assert.Equal(t, REDACTED, sliceSSL["key"]) } + +func TestRedactEnv(t *testing.T) { + tests := []struct { + name string + env map[string]string + expect map[string]any + }{{ + name: "No redactions", + env: map[string]string{ + "TEST_LEVEL": "test-val", + "VAL1": "a,b,c", + }, + expect: map[string]any{ + "TEST_LEVEL": "test-val", + "VAL1": "a,b,c", + }, + }, { + name: "Redacts value based on key", + env: map[string]string{ + "TEST_LEVEL": "test-val", + "VAL1": "a,b,c", + "API_KEY": "secret-value", + "SERVICE_TOKEN": "secret-token", + }, + expect: map[string]any{ + "TEST_LEVEL": "test-val", + "VAL1": "a,b,c", + "API_KEY": REDACTED, + "SERVICE_TOKEN": REDACTED, + }, + }} + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + for k, v := range tt.env { + t.Setenv(k, v) + } + redacted, err := redactEnv() + require.NoError(t, err) + + // redacted includes full env vars, we want to check if the expected k:v's are present + for k, v := range tt.expect { + require.Contains(t, redacted, k) + require.Equal(t, v, redacted[k]) + } + }) + } +} diff --git a/testing/integration/ess/diagnostics_test.go b/testing/integration/ess/diagnostics_test.go index ee50b25dda5..e0bd3383410 100644 --- a/testing/integration/ess/diagnostics_test.go +++ b/testing/integration/ess/diagnostics_test.go @@ -46,6 +46,7 @@ var diagnosticsFiles = []string{ "components-actual.yaml", "components-expected.yaml", "computed-config.yaml", + "environment.yaml", "goroutine.pprof.gz", "heap.pprof.gz", "local-config.yaml",