Skip to content

Commit 45f36e8

Browse files
authored
Pass --header enrollment option to fleet-server (#8071) (#8809)
1 parent 27e9eee commit 45f36e8

File tree

12 files changed

+208
-23
lines changed

12 files changed

+208
-23
lines changed
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
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: bug-fix
12+
13+
# Change summary; a 80ish characters long description of the change.
14+
summary: Use --header from enrollment when communicating with Fleet Server
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+
The --header option for the enrollment command now adds the headers to the communication with Fleet Server. This
21+
allows a proxy that requires specific headers present for traffic to flow to be placed in front of a Fleet Server
22+
to be used and still allowing the Elastic Agent to enroll.
23+
24+
# Affected component; usually one of "elastic-agent", "fleet-server", "filebeat", "metricbeat", "auditbeat", "all", etc.
25+
component: elastic-agent
26+
27+
# PR URL; optional; the PR number that added the changeset.
28+
# If not present is automatically filled by the tooling finding the PR where this changelog fragment has been added.
29+
# NOTE: the tooling supports backports, so it's able to fill the original PR number instead of the backport PR number.
30+
# Please provide it if you are adding a fragment for a different PR.
31+
pr: https://github.com/elastic/elastic-agent/pull/8071
32+
33+
# Issue URL; optional; the GitHub issue related to this changeset (either closes or is part of).
34+
# If not present is automatically filled by the tooling with the issue linked to the PR number.
35+
issue: https://github.com/elastic/elastic-agent/issues/6823

internal/pkg/agent/cmd/container.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -501,6 +501,9 @@ func buildEnrollArgs(cfg setupConfig, token string, policyID string) ([]string,
501501
if cfg.Fleet.Insecure {
502502
args = append(args, "--insecure")
503503
}
504+
for k, v := range cfg.Fleet.Headers {
505+
args = append(args, "--header", k+"="+v)
506+
}
504507
}
505508
if cfg.Fleet.CA != "" {
506509
args = append(args, "--certificate-authorities", cfg.Fleet.CA)

internal/pkg/agent/cmd/enroll.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ func addEnrollFlags(cmd *cobra.Command) {
8484
cmd.Flags().StringP("fleet-server-cert-key", "", "", "Private key for the certificate used by Fleet Server for exposed HTTPS endpoint")
8585
cmd.Flags().StringP("fleet-server-cert-key-passphrase", "", "", "Path for private key passphrase file used to decrypt Fleet Server certificate key")
8686
cmd.Flags().StringP("fleet-server-client-auth", "", "none", "Fleet Server mTLS client authentication for connecting Elastic Agents. Must be one of [none, optional, required]")
87-
cmd.Flags().StringSliceP("header", "", []string{}, "Headers used by Fleet Server when communicating with Elasticsearch")
87+
cmd.Flags().StringSliceP("header", "", []string{}, "Headers used by Agent to communicate with Fleet Server, and when a bootstrapped Fleet Server communicates with Elasticsearch")
8888
cmd.Flags().BoolP("fleet-server-insecure-http", "", false, "Expose Fleet Server over HTTP (not recommended; insecure)")
8989
cmd.Flags().StringP("certificate-authorities", "a", "", "Comma-separated list of root certificates for server verification used by Elastic Agent and Fleet Server")
9090
cmd.Flags().StringP("ca-sha256", "p", "", "Comma-separated list of certificate authority hash pins for server verification used by Elastic Agent and Fleet Server")
@@ -522,6 +522,7 @@ func enroll(streams *cli.IOStreams, cmd *cobra.Command) error {
522522
UserProvidedMetadata: make(map[string]interface{}),
523523
Staging: staging,
524524
FixPermissions: fixPermissions,
525+
Headers: mapFromEnvList(fHeaders),
525526
ProxyURL: proxyURL,
526527
ProxyDisabled: proxyDisabled,
527528
ProxyHeaders: mapFromEnvList(proxyHeaders),

internal/pkg/agent/cmd/enroll_cmd.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,7 @@ type enrollCmdOption struct {
122122
ReplaceToken string `yaml:"replace_token,omitempty"`
123123
EnrollAPIKey string `yaml:"enrollment_key,omitempty"`
124124
Staging string `yaml:"staging,omitempty"`
125+
Headers map[string]string `yaml:"headers,omitempty"`
125126
ProxyURL string `yaml:"proxy_url,omitempty"`
126127
ProxyDisabled bool `yaml:"proxy_disabled,omitempty"`
127128
ProxyHeaders map[string]string `yaml:"proxy_headers,omitempty"`
@@ -144,6 +145,7 @@ func (e *enrollCmdOption) remoteConfig() (remote.Config, error) {
144145
if cfg.Protocol == remote.ProtocolHTTP && !e.Insecure {
145146
return remote.Config{}, fmt.Errorf("connection to fleet-server is insecure, strongly recommended to use a secure connection (override with --insecure)")
146147
}
148+
cfg.Headers = e.Headers
147149

148150
var tlsCfg tlscommon.Config
149151

internal/pkg/agent/cmd/enroll_cmd_test.go

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -467,6 +467,79 @@ func TestEnroll(t *testing.T) {
467467
store.AssertExpectations(t)
468468
},
469469
))
470+
471+
t.Run("headers are sent to server", withServer(
472+
func(t *testing.T) *http.ServeMux {
473+
mux := http.NewServeMux()
474+
mux.HandleFunc("/api/fleet/agents/enroll", func(w http.ResponseWriter, r *http.Request) {
475+
if r.Header.Get("X-Test-Header") != "Test-Value" {
476+
w.WriteHeader(http.StatusInternalServerError)
477+
_, _ = w.Write([]byte(`
478+
{
479+
"statusCode": 500,
480+
"error": "Missing required X-Test-Header header"
481+
}`))
482+
return
483+
}
484+
w.WriteHeader(http.StatusOK)
485+
_, _ = w.Write([]byte(`
486+
{
487+
"action": "created",
488+
"item": {
489+
"id": "a9328860-ec54-11e9-93c4-d72ab8a69391",
490+
"active": true,
491+
"policy_id": "69f3f5a0-ec52-11e9-93c4-d72ab8a69391",
492+
"type": "PERMANENT",
493+
"enrolled_at": "2019-10-11T18:26:37.158Z",
494+
"user_provided_metadata": {
495+
"custom": "customize"
496+
},
497+
"local_metadata": {
498+
"platform": "linux",
499+
"version": "8.0.0"
500+
},
501+
"actions": [],
502+
"access_api_key": "my-access-api-key"
503+
}
504+
}`))
505+
})
506+
return mux
507+
}, func(t *testing.T, host string) {
508+
url := "http://" + host
509+
store := &mockStore{}
510+
cmd, err := newEnrollCmd(
511+
log,
512+
&enrollCmdOption{
513+
URL: url,
514+
CAs: []string{},
515+
EnrollAPIKey: "my-enrollment-api-key",
516+
Insecure: true,
517+
UserProvidedMetadata: map[string]interface{}{"custom": "customize"},
518+
SkipCreateSecret: skipCreateSecret,
519+
SkipDaemonRestart: true,
520+
Headers: map[string]string{
521+
"X-Test-Header": "Test-Value",
522+
},
523+
},
524+
"",
525+
store,
526+
nil,
527+
)
528+
require.NoError(t, err)
529+
530+
streams, _, _, _ := cli.NewTestingIOStreams()
531+
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Minute)
532+
defer cancel()
533+
err = cmd.Execute(ctx, streams)
534+
require.NoError(t, err, "enroll command should return no error")
535+
536+
assert.True(t, store.Called, "the store should have been called")
537+
config, err := readConfig(store.Content)
538+
require.NoError(t, err)
539+
assert.Equal(t, "my-access-api-key", config.AccessAPIKey)
540+
assert.Equal(t, host, config.Client.Host)
541+
},
542+
))
470543
}
471544

472545
func TestValidateArgs(t *testing.T) {

internal/pkg/agent/cmd/setup_config.go

Lines changed: 16 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -15,20 +15,21 @@ type setupConfig struct {
1515
}
1616

1717
type fleetConfig struct {
18-
CA string `config:"ca"`
19-
Enroll bool `config:"enroll"`
20-
EnrollmentToken string `config:"enrollment_token"`
21-
ID string `config:"id"`
22-
ReplaceToken string `config:"replace_token"`
23-
Force bool `config:"force"`
24-
Insecure bool `config:"insecure"`
25-
TokenName string `config:"token_name"`
26-
TokenPolicyName string `config:"token_policy_name"`
27-
URL string `config:"url"`
28-
DaemonTimeout time.Duration `config:"daemon_timeout"`
29-
EnrollTimeout time.Duration `config:"enroll_timeout"`
30-
Cert string `config:"cert"`
31-
CertKey string `config:"cert_key"`
18+
CA string `config:"ca"`
19+
Enroll bool `config:"enroll"`
20+
EnrollmentToken string `config:"enrollment_token"`
21+
ID string `config:"id"`
22+
ReplaceToken string `config:"replace_token"`
23+
Force bool `config:"force"`
24+
Insecure bool `config:"insecure"`
25+
TokenName string `config:"token_name"`
26+
TokenPolicyName string `config:"token_policy_name"`
27+
URL string `config:"url"`
28+
Headers map[string]string `config:"headers"`
29+
DaemonTimeout time.Duration `config:"daemon_timeout"`
30+
EnrollTimeout time.Duration `config:"enroll_timeout"`
31+
Cert string `config:"cert"`
32+
CertKey string `config:"cert_key"`
3233
}
3334

3435
type fleetServerConfig struct {
@@ -96,6 +97,7 @@ func defaultAccessConfig() (setupConfig, error) {
9697
TokenName: envWithDefault("Default", "FLEET_TOKEN_NAME"),
9798
TokenPolicyName: envWithDefault("", "FLEET_TOKEN_POLICY_NAME"),
9899
URL: envWithDefault("", "FLEET_URL"),
100+
Headers: envMap("FLEET_HEADER"),
99101
DaemonTimeout: envTimeout("FLEET_DAEMON_TIMEOUT"),
100102
EnrollTimeout: envTimeout("FLEET_ENROLL_TIMEOUT"),
101103
Cert: envWithDefault("", "ELASTIC_AGENT_CERT"),

internal/pkg/diagnostics/diagnostics.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -398,7 +398,8 @@ func redactKey(k string) bool {
398398
}
399399

400400
k = strings.ToLower(k)
401-
return strings.Contains(k, "certificate") ||
401+
return strings.Contains(k, "auth") ||
402+
strings.Contains(k, "certificate") ||
402403
strings.Contains(k, "passphrase") ||
403404
strings.Contains(k, "password") ||
404405
strings.Contains(k, "token") ||

internal/pkg/diagnostics/diagnostics_test.go

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -89,8 +89,10 @@ i4EFZLWrFRsAAAARYWxleGtAZ3JlbWluLm5lc3QBAg==
8989
"nested1": mapstr.M{
9090
"certificate": "unredacted",
9191
"nested2": mapstr.M{
92-
"passphrase": "unredacted",
93-
"password": "unredacted",
92+
"X-Authentication": "unredacted",
93+
"X-App-Auth": "unredacted",
94+
"passphrase": "unredacted",
95+
"password": "unredacted",
9496
"nested3": mapstr.M{
9597
"token": "unredacted",
9698
"key": "unredacted",

internal/pkg/fleetapi/checkin_cmd_test.go

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import (
1818

1919
"github.com/elastic/elastic-agent/internal/pkg/agent/application/info"
2020
"github.com/elastic/elastic-agent/internal/pkg/fleetapi/client"
21+
"github.com/elastic/elastic-agent/internal/pkg/remote"
2122
)
2223

2324
type agentinfo struct{}
@@ -269,4 +270,48 @@ func TestCheckin(t *testing.T) {
269270
require.Equal(t, 0, len(r.Actions))
270271
},
271272
))
273+
274+
t.Run("Headers are sent", withServerWithAuthClient(
275+
func(t *testing.T) *http.ServeMux {
276+
raw := `{"actions": []}`
277+
mux := http.NewServeMux()
278+
path := fmt.Sprintf("/api/fleet/agents/%s/checkin", agentInfo.AgentID())
279+
mux.HandleFunc(path, authHandler(func(w http.ResponseWriter, r *http.Request) {
280+
type Request struct {
281+
Metadata *info.ECSMeta `json:"local_metadata"`
282+
}
283+
284+
var req *Request
285+
286+
content, err := io.ReadAll(r.Body)
287+
assert.NoError(t, err)
288+
assert.NoError(t, json.Unmarshal(content, &req))
289+
assert.Nil(t, req.Metadata)
290+
291+
authHeader, ok := r.Header["X-App-Auth"]
292+
if assert.True(t, ok) && assert.Len(t, authHeader, 1) {
293+
assert.Equal(t, "auth-token-123", authHeader[0])
294+
}
295+
296+
w.WriteHeader(http.StatusOK)
297+
fmt.Fprint(w, raw)
298+
}, withAPIKey))
299+
return mux
300+
}, withAPIKey,
301+
func(t *testing.T, client client.Sender) {
302+
cmd := NewCheckinCmd(agentInfo, client)
303+
304+
request := CheckinRequest{}
305+
306+
r, _, err := cmd.Execute(ctx, &request)
307+
require.NoError(t, err)
308+
309+
require.Equal(t, 0, len(r.Actions))
310+
},
311+
func(config *remote.Config) {
312+
config.Headers = map[string]string{
313+
"X-App-Auth": "auth-token-123",
314+
}
315+
},
316+
))
272317
}

internal/pkg/fleetapi/helper_test.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,13 +43,17 @@ func withServerWithAuthClient(
4343
m func(t *testing.T) *http.ServeMux,
4444
apiKey string,
4545
test func(t *testing.T, client client.Sender),
46+
configMod ...func(*remote.Config),
4647
) func(t *testing.T) {
4748

4849
return withServer(m, func(t *testing.T, host string) {
4950
log, _ := logger.New("", false)
5051
cfg := remote.Config{
5152
Host: host,
5253
}
54+
for _, mod := range configMod {
55+
mod(&cfg)
56+
}
5357

5458
client, err := client.NewAuthWithConfig(log, apiKey, cfg)
5559
require.NoError(t, err)

0 commit comments

Comments
 (0)