From c9f0fc8c41201e24316a9e22d30f1a021c67204a Mon Sep 17 00:00:00 2001 From: Michal Pristas Date: Fri, 18 Jun 2021 10:00:22 +0200 Subject: [PATCH] Cherry-pick #26275 to 7.13: Enable agent to send custom headers to kibana/ES (#26362) * update * Update CHANGELOG.next.asciidoc --- x-pack/elastic-agent/CHANGELOG.next.asciidoc | 1 + .../pkg/agent/application/info/agent_id.go | 1 + .../pkg/agent/application/info/agent_info.go | 7 + .../elastic-agent/pkg/agent/cmd/container.go | 142 ++++-------------- x-pack/elastic-agent/pkg/agent/cmd/enroll.go | 24 +++ .../elastic-agent/pkg/agent/cmd/enroll_cmd.go | 31 +++- .../pkg/agent/cmd/setup_config.go | 119 +++++++++++++++ .../pkg/agent/configuration/fleet_server.go | 1 + .../pkg/agent/program/program_test.go | 6 + .../pkg/agent/program/supported.go | 2 +- .../pkg/agent/transpiler/rules.go | 69 +++++++++ .../pkg/agent/transpiler/rules_test.go | 68 +++++++++ x-pack/elastic-agent/spec/apm-server.yml | 1 + x-pack/elastic-agent/spec/filebeat.yml | 3 + x-pack/elastic-agent/spec/fleet-server.yml | 2 + x-pack/elastic-agent/spec/metricbeat.yml | 1 + 16 files changed, 361 insertions(+), 117 deletions(-) create mode 100644 x-pack/elastic-agent/pkg/agent/cmd/setup_config.go diff --git a/x-pack/elastic-agent/CHANGELOG.next.asciidoc b/x-pack/elastic-agent/CHANGELOG.next.asciidoc index 439b72f6dcb..2781e9778ab 100644 --- a/x-pack/elastic-agent/CHANGELOG.next.asciidoc +++ b/x-pack/elastic-agent/CHANGELOG.next.asciidoc @@ -110,3 +110,4 @@ - Add --fleet-server-service-token and FLEET_SERVER_SERVICE_TOKEN options {pull}25083[25083] - Keep http and logging config during enroll {pull}25132[25132] - Log output of container to $LOGS_PATH/elastic-agent-start.log when LOGS_PATH set {pull}25150[25150] +- Enable agent to send custom headers to kibana/ES {pull}26275[26275] diff --git a/x-pack/elastic-agent/pkg/agent/application/info/agent_id.go b/x-pack/elastic-agent/pkg/agent/application/info/agent_id.go index 386beabca61..6b62f32d396 100644 --- a/x-pack/elastic-agent/pkg/agent/application/info/agent_id.go +++ b/x-pack/elastic-agent/pkg/agent/application/info/agent_id.go @@ -29,6 +29,7 @@ const maxRetriesloadAgentInfo = 5 type persistentAgentInfo struct { ID string `json:"id" yaml:"id" config:"id"` + Headers map[string]string `json:"headers" yaml:"headers" config:"headers"` LogLevel string `json:"logging.level,omitempty" yaml:"logging.level,omitempty" config:"logging.level,omitempty"` MonitoringHTTP *monitoringConfig.MonitoringHTTPConfig `json:"monitoring.http,omitempty" yaml:"monitoring.http,omitempty" config:"monitoring.http,omitempty"` } diff --git a/x-pack/elastic-agent/pkg/agent/application/info/agent_info.go b/x-pack/elastic-agent/pkg/agent/application/info/agent_info.go index 8ae09c2efc3..8ae562919f3 100644 --- a/x-pack/elastic-agent/pkg/agent/application/info/agent_info.go +++ b/x-pack/elastic-agent/pkg/agent/application/info/agent_info.go @@ -13,6 +13,7 @@ import ( type AgentInfo struct { agentID string logLevel string + headers map[string]string } // NewAgentInfoWithLog creates a new agent information. @@ -30,6 +31,7 @@ func NewAgentInfoWithLog(level string, createAgentID bool) (*AgentInfo, error) { return &AgentInfo{ agentID: agentInfo.ID, logLevel: agentInfo.LogLevel, + headers: agentInfo.Headers, }, nil } @@ -84,3 +86,8 @@ func (*AgentInfo) Version() string { func (*AgentInfo) Snapshot() bool { return release.Snapshot() } + +// Headers returns custom headers used to communicate with elasticsearch. +func (i *AgentInfo) Headers() map[string]string { + return i.headers +} diff --git a/x-pack/elastic-agent/pkg/agent/cmd/container.go b/x-pack/elastic-agent/pkg/agent/cmd/container.go index 8f6f8b43fb0..26a14b87fd8 100644 --- a/x-pack/elastic-agent/pkg/agent/cmd/container.go +++ b/x-pack/elastic-agent/pkg/agent/cmd/container.go @@ -271,7 +271,7 @@ func runContainerCmd(streams *cli.IOStreams, cmd *cobra.Command, cfg setupConfig } if cfg.Kibana.Fleet.Setup { - client, err = kibanaClient(cfg.Kibana) + client, err = kibanaClient(cfg.Kibana, cfg.Kibana.Headers) if err != nil { return err } @@ -286,7 +286,7 @@ func runContainerCmd(streams *cli.IOStreams, cmd *cobra.Command, cfg setupConfig token := cfg.Fleet.EnrollmentToken if token == "" && !cfg.FleetServer.Enable { if client == nil { - client, err = kibanaClient(cfg.Kibana) + client, err = kibanaClient(cfg.Kibana, cfg.Kibana.Headers) if err != nil { return err } @@ -363,6 +363,11 @@ func buildEnrollArgs(cfg setupConfig, token string, policyID string) ([]string, if cfg.FleetServer.CertKey != "" { args = append(args, "--fleet-server-cert-key", cfg.FleetServer.CertKey) } + + for k, v := range cfg.FleetServer.Headers { + args = append(args, "--header", k+"="+v) + } + if cfg.Fleet.URL != "" { args = append(args, "--url", cfg.Fleet.URL) } @@ -444,19 +449,21 @@ func kibanaFetchToken(cfg setupConfig, client *kibana.Client, policy *kibanaPoli return keyDetail.Item.APIKey, nil } -func kibanaClient(cfg kibanaConfig) (*kibana.Client, error) { +func kibanaClient(cfg kibanaConfig, headers map[string]string) (*kibana.Client, error) { var tls *tlscommon.Config if cfg.Fleet.CA != "" { tls = &tlscommon.Config{ CAs: []string{cfg.Fleet.CA}, } } + return kibana.NewClientWithConfig(&kibana.ClientConfig{ Host: cfg.Fleet.Host, Username: cfg.Fleet.Username, Password: cfg.Fleet.Password, IgnoreVersion: true, TLS: tls, + Headers: headers, }) } @@ -518,6 +525,27 @@ func envBool(keys ...string) bool { return false } +func envMap(key string) map[string]string { + m := make(map[string]string) + prefix := key + "=" + for _, env := range os.Environ() { + if !strings.HasPrefix(env, prefix) { + continue + } + + envVal := strings.TrimPrefix(env, prefix) + + keyValue := strings.SplitN(envVal, "=", 2) + if len(keyValue) != 2 { + continue + } + + m[keyValue[0]] = keyValue[1] + } + + return m +} + func isTrue(val string) bool { trueVals := []string{"1", "true", "yes", "y"} val = strings.ToLower(val) @@ -815,114 +843,6 @@ type kibanaAPIKeyDetail struct { Item kibanaAPIKey `json:"item"` } -// setup configuration - -type setupConfig struct { - Fleet fleetConfig `config:"fleet"` - FleetServer fleetServerConfig `config:"fleet_server"` - Kibana kibanaConfig `config:"kibana"` -} - -type elasticsearchConfig struct { - CA string `config:"ca"` - Host string `config:"host"` - Username string `config:"username"` - Password string `config:"password"` - ServiceToken string `config:"service_token"` -} - -type fleetConfig struct { - CA string `config:"ca"` - Enroll bool `config:"enroll"` - EnrollmentToken string `config:"enrollment_token"` - Force bool `config:"force"` - Insecure bool `config:"insecure"` - TokenName string `config:"token_name"` - TokenPolicyName string `config:"token_policy_name"` - URL string `config:"url"` -} - -type fleetServerConfig struct { - Cert string `config:"cert"` - CertKey string `config:"cert_key"` - Elasticsearch elasticsearchConfig `config:"elasticsearch"` - Enable bool `config:"enable"` - Host string `config:"host"` - InsecureHTTP bool `config:"insecure_http"` - PolicyID string `config:"policy_id"` - Port string `config:"port"` -} - -type kibanaConfig struct { - Fleet kibanaFleetConfig `config:"fleet"` - RetrySleepDuration time.Duration `config:"retry_sleep_duration"` - RetryMaxCount int `config:"retry_max_count"` -} - -type kibanaFleetConfig struct { - CA string `config:"ca"` - Host string `config:"host"` - Password string `config:"password"` - Setup bool `config:"setup"` - Username string `config:"username"` -} - -func defaultAccessConfig() (setupConfig, error) { - retrySleepDuration, err := envDurationWithDefault(defaultRequestRetrySleep, requestRetrySleepEnv) - if err != nil { - return setupConfig{}, err - } - - retryMaxCount, err := envIntWithDefault(defaultMaxRequestRetries, maxRequestRetriesEnv) - if err != nil { - return setupConfig{}, err - } - - cfg := setupConfig{ - Fleet: fleetConfig{ - CA: envWithDefault("", "FLEET_CA", "KIBANA_CA", "ELASTICSEARCH_CA"), - Enroll: envBool("FLEET_ENROLL", "FLEET_SERVER_ENABLE"), - EnrollmentToken: envWithDefault("", "FLEET_ENROLLMENT_TOKEN"), - Force: envBool("FLEET_FORCE"), - Insecure: envBool("FLEET_INSECURE"), - TokenName: envWithDefault("Default", "FLEET_TOKEN_NAME"), - TokenPolicyName: envWithDefault("", "FLEET_TOKEN_POLICY_NAME"), - URL: envWithDefault("", "FLEET_URL"), - }, - FleetServer: fleetServerConfig{ - Cert: envWithDefault("", "FLEET_SERVER_CERT"), - CertKey: envWithDefault("", "FLEET_SERVER_CERT_KEY"), - Elasticsearch: elasticsearchConfig{ - Host: envWithDefault("http://elasticsearch:9200", "FLEET_SERVER_ELASTICSEARCH_HOST", "ELASTICSEARCH_HOST"), - Username: envWithDefault("elastic", "FLEET_SERVER_ELASTICSEARCH_USERNAME", "ELASTICSEARCH_USERNAME"), - Password: envWithDefault("changeme", "FLEET_SERVER_ELASTICSEARCH_PASSWORD", "ELASTICSEARCH_PASSWORD"), - ServiceToken: envWithDefault("", "FLEET_SERVER_SERVICE_TOKEN"), - CA: envWithDefault("", "FLEET_SERVER_ELASTICSEARCH_CA", "ELASTICSEARCH_CA"), - }, - Enable: envBool("FLEET_SERVER_ENABLE"), - Host: envWithDefault("", "FLEET_SERVER_HOST"), - InsecureHTTP: envBool("FLEET_SERVER_INSECURE_HTTP"), - PolicyID: envWithDefault("", "FLEET_SERVER_POLICY_ID", "FLEET_SERVER_POLICY"), - Port: envWithDefault("", "FLEET_SERVER_PORT"), - }, - Kibana: kibanaConfig{ - Fleet: kibanaFleetConfig{ - // Remove FLEET_SETUP in 8.x - // The FLEET_SETUP environment variable boolean is a fallback to the old name. The name was updated to - // reflect that its setting up Fleet in Kibana versus setting up Fleet Server. - Setup: envBool("KIBANA_FLEET_SETUP", "FLEET_SETUP"), - Host: envWithDefault("http://kibana:5601", "KIBANA_FLEET_HOST", "KIBANA_HOST"), - Username: envWithDefault("elastic", "KIBANA_FLEET_USERNAME", "KIBANA_USERNAME", "ELASTICSEARCH_USERNAME"), - Password: envWithDefault("changeme", "KIBANA_FLEET_PASSWORD", "KIBANA_PASSWORD", "ELASTICSEARCH_PASSWORD"), - CA: envWithDefault("", "KIBANA_FLEET_CA", "KIBANA_CA", "ELASTICSEARCH_CA"), - }, - RetrySleepDuration: retrySleepDuration, - RetryMaxCount: retryMaxCount, - }, - } - return cfg, nil -} - func envDurationWithDefault(defVal string, keys ...string) (time.Duration, error) { valStr := defVal for _, key := range keys { diff --git a/x-pack/elastic-agent/pkg/agent/cmd/enroll.go b/x-pack/elastic-agent/pkg/agent/cmd/enroll.go index 55baf76f56a..2828bc4f0da 100644 --- a/x-pack/elastic-agent/pkg/agent/cmd/enroll.go +++ b/x-pack/elastic-agent/pkg/agent/cmd/enroll.go @@ -10,6 +10,7 @@ import ( "os" "os/signal" "strconv" + "strings" "syscall" "github.com/elastic/beats/v7/x-pack/elastic-agent/pkg/agent/application/paths" @@ -59,6 +60,7 @@ func addEnrollFlags(cmd *cobra.Command) { cmd.Flags().Uint16P("fleet-server-port", "", 0, "Fleet Server HTTP binding port (overrides the policy)") cmd.Flags().StringP("fleet-server-cert", "", "", "Certificate to use for exposed Fleet Server HTTPS endpoint") cmd.Flags().StringP("fleet-server-cert-key", "", "", "Private key to use for exposed Fleet Server HTTPS endpoint") + cmd.Flags().StringSliceP("header", "", []string{}, "Headers used in communication with elasticsearch") cmd.Flags().BoolP("fleet-server-insecure-http", "", false, "Expose Fleet Server over HTTP (not recommended; insecure)") cmd.Flags().StringP("certificate-authorities", "a", "", "Comma separated list of root certificate for server verifications") cmd.Flags().StringP("ca-sha256", "p", "", "Comma separated list of certificate authorities hash pins used for certificate verifications") @@ -81,6 +83,7 @@ func buildEnrollmentFlags(cmd *cobra.Command, url string, token string) []string fPort, _ := cmd.Flags().GetUint16("fleet-server-port") fCert, _ := cmd.Flags().GetString("fleet-server-cert") fCertKey, _ := cmd.Flags().GetString("fleet-server-cert-key") + fHeaders, _ := cmd.Flags().GetStringSlice("header") fInsecure, _ := cmd.Flags().GetBool("fleet-server-insecure-http") ca, _ := cmd.Flags().GetString("certificate-authorities") sha256, _ := cmd.Flags().GetString("ca-sha256") @@ -128,6 +131,12 @@ func buildEnrollmentFlags(cmd *cobra.Command, url string, token string) []string args = append(args, "--fleet-server-cert-key") args = append(args, fCertKey) } + + for k, v := range mapFromEnvList(fHeaders) { + args = append(args, "--header") + args = append(args, k+"="+v) + } + if fInsecure { args = append(args, "--fleet-server-insecure-http") } @@ -211,6 +220,7 @@ func enroll(streams *cli.IOStreams, cmd *cobra.Command, args []string) error { enrollmentToken, _ := cmd.Flags().GetString("enrollment-token") fServer, _ := cmd.Flags().GetString("fleet-server-es") fElasticSearchCA, _ := cmd.Flags().GetString("fleet-server-es-ca") + fHeaders, _ := cmd.Flags().GetStringSlice("header") fServiceToken, _ := cmd.Flags().GetString("fleet-server-service-token") fPolicy, _ := cmd.Flags().GetString("fleet-server-policy") fHost, _ := cmd.Flags().GetString("fleet-server-host") @@ -246,6 +256,7 @@ func enroll(streams *cli.IOStreams, cmd *cobra.Command, args []string) error { CertKey: fCertKey, Insecure: fInsecure, SpawnAgent: !fromInstall, + Headers: mapFromEnvList(fHeaders), }, } @@ -285,3 +296,16 @@ func handleSignal(ctx context.Context) context.Context { return ctx } + +func mapFromEnvList(envList []string) map[string]string { + m := make(map[string]string) + for _, kv := range envList { + keyValue := strings.SplitN(kv, "=", 2) + if len(keyValue) != 2 { + continue + } + + m[keyValue[0]] = keyValue[1] + } + return m +} diff --git a/x-pack/elastic-agent/pkg/agent/cmd/enroll_cmd.go b/x-pack/elastic-agent/pkg/agent/cmd/enroll_cmd.go index 9bb74d4d01a..cd3c9581c0b 100644 --- a/x-pack/elastic-agent/pkg/agent/cmd/enroll_cmd.go +++ b/x-pack/elastic-agent/pkg/agent/cmd/enroll_cmd.go @@ -78,6 +78,7 @@ type enrollCmdFleetServerOption struct { CertKey string Insecure bool SpawnAgent bool + Headers map[string]string } // enrollCmdOption define all the supported enrollment option. @@ -232,7 +233,8 @@ func (c *enrollCmd) fleetServerBootstrap(ctx context.Context) (string, error) { c.options.FleetServer.ConnStr, c.options.FleetServer.ServiceToken, c.options.FleetServer.PolicyID, c.options.FleetServer.Host, c.options.FleetServer.Port, - c.options.FleetServer.Cert, c.options.FleetServer.CertKey, c.options.FleetServer.ElasticsearchCA) + c.options.FleetServer.Cert, c.options.FleetServer.CertKey, c.options.FleetServer.ElasticsearchCA, + c.options.FleetServer.Headers) if err != nil { return "", err } @@ -412,7 +414,7 @@ func (c *enrollCmd) enroll(ctx context.Context, persistentConfig map[string]inte return err } - agentConfig, err := c.createAgentConfig(resp.Item.ID, persistentConfig) + agentConfig, err := c.createAgentConfig(resp.Item.ID, persistentConfig, c.options.FleetServer.Headers) if err != nil { return err } @@ -422,7 +424,8 @@ func (c *enrollCmd) enroll(ctx context.Context, persistentConfig map[string]inte c.options.FleetServer.ConnStr, c.options.FleetServer.ServiceToken, c.options.FleetServer.PolicyID, c.options.FleetServer.Host, c.options.FleetServer.Port, - c.options.FleetServer.Cert, c.options.FleetServer.CertKey, c.options.FleetServer.ElasticsearchCA) + c.options.FleetServer.Cert, c.options.FleetServer.CertKey, c.options.FleetServer.ElasticsearchCA, + c.options.FleetServer.Headers) if err != nil { return err } @@ -717,7 +720,12 @@ func storeAgentInfo(s saver, reader io.Reader) error { return nil } -func createFleetServerBootstrapConfig(connStr string, serviceToken string, policyID string, host string, port uint16, cert string, key string, esCA string) (*configuration.FleetAgentConfig, error) { +func createFleetServerBootstrapConfig( + connStr, serviceToken, policyID, host string, + port uint16, + cert, key, esCA string, + headers map[string]string, +) (*configuration.FleetAgentConfig, error) { es, err := configuration.ElasticsearchFromConnStr(connStr, serviceToken) if err != nil { return nil, err @@ -733,6 +741,15 @@ func createFleetServerBootstrapConfig(connStr string, serviceToken string, polic if port == 0 { port = defaultFleetServerPort } + if len(headers) > 0 { + if es.Headers == nil { + es.Headers = make(map[string]string) + } + // overwrites previously set headers + for k, v := range headers { + es.Headers[k] = v + } + } cfg := configuration.DefaultFleetAgentConfig() cfg.Enabled = true cfg.Server = &configuration.FleetServerConfig{ @@ -773,11 +790,15 @@ func createFleetConfigFromEnroll(accessAPIKey string, cli remote.Config) (*confi return cfg, nil } -func (c *enrollCmd) createAgentConfig(agentID string, pc map[string]interface{}) (map[string]interface{}, error) { +func (c *enrollCmd) createAgentConfig(agentID string, pc map[string]interface{}, headers map[string]string) (map[string]interface{}, error) { agentConfig := map[string]interface{}{ "id": agentID, } + if len(headers) > 0 { + agentConfig["headers"] = headers + } + if c.options.Staging != "" { staging := fmt.Sprintf("https://staging.elastic.co/%s-%s/downloads/", release.Version(), c.options.Staging[:8]) agentConfig["download"] = map[string]interface{}{ diff --git a/x-pack/elastic-agent/pkg/agent/cmd/setup_config.go b/x-pack/elastic-agent/pkg/agent/cmd/setup_config.go new file mode 100644 index 00000000000..4330c967e9f --- /dev/null +++ b/x-pack/elastic-agent/pkg/agent/cmd/setup_config.go @@ -0,0 +1,119 @@ +// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +// or more contributor license agreements. Licensed under the Elastic License; +// you may not use this file except in compliance with the Elastic License. + +package cmd + +import "time" + +// setup configuration + +type setupConfig struct { + Fleet fleetConfig `config:"fleet"` + FleetServer fleetServerConfig `config:"fleet_server"` + Kibana kibanaConfig `config:"kibana"` +} + +type fleetConfig struct { + CA string `config:"ca"` + Enroll bool `config:"enroll"` + EnrollmentToken string `config:"enrollment_token"` + Force bool `config:"force"` + Insecure bool `config:"insecure"` + TokenName string `config:"token_name"` + TokenPolicyName string `config:"token_policy_name"` + URL string `config:"url"` +} + +type fleetServerConfig struct { + Cert string `config:"cert"` + CertKey string `config:"cert_key"` + Elasticsearch elasticsearchConfig `config:"elasticsearch"` + Enable bool `config:"enable"` + Host string `config:"host"` + InsecureHTTP bool `config:"insecure_http"` + PolicyID string `config:"policy_id"` + Port string `config:"port"` + Headers map[string]string `config:"headers"` +} + +type elasticsearchConfig struct { + CA string `config:"ca"` + Host string `config:"host"` + Username string `config:"username"` + Password string `config:"password"` + ServiceToken string `config:"service_token"` +} + +type kibanaConfig struct { + Fleet kibanaFleetConfig `config:"fleet"` + RetrySleepDuration time.Duration `config:"retry_sleep_duration"` + RetryMaxCount int `config:"retry_max_count"` + Headers map[string]string `config:"headers"` +} + +type kibanaFleetConfig struct { + CA string `config:"ca"` + Host string `config:"host"` + Password string `config:"password"` + Setup bool `config:"setup"` + Username string `config:"username"` +} + +func defaultAccessConfig() (setupConfig, error) { + retrySleepDuration, err := envDurationWithDefault(defaultRequestRetrySleep, requestRetrySleepEnv) + if err != nil { + return setupConfig{}, err + } + + retryMaxCount, err := envIntWithDefault(defaultMaxRequestRetries, maxRequestRetriesEnv) + if err != nil { + return setupConfig{}, err + } + + cfg := setupConfig{ + Fleet: fleetConfig{ + CA: envWithDefault("", "FLEET_CA", "KIBANA_CA", "ELASTICSEARCH_CA"), + Enroll: envBool("FLEET_ENROLL", "FLEET_SERVER_ENABLE"), + EnrollmentToken: envWithDefault("", "FLEET_ENROLLMENT_TOKEN"), + Force: envBool("FLEET_FORCE"), + Insecure: envBool("FLEET_INSECURE"), + TokenName: envWithDefault("Default", "FLEET_TOKEN_NAME"), + TokenPolicyName: envWithDefault("", "FLEET_TOKEN_POLICY_NAME"), + URL: envWithDefault("", "FLEET_URL"), + }, + FleetServer: fleetServerConfig{ + Cert: envWithDefault("", "FLEET_SERVER_CERT"), + CertKey: envWithDefault("", "FLEET_SERVER_CERT_KEY"), + Elasticsearch: elasticsearchConfig{ + Host: envWithDefault("http://elasticsearch:9200", "FLEET_SERVER_ELASTICSEARCH_HOST", "ELASTICSEARCH_HOST"), + Username: envWithDefault("elastic", "FLEET_SERVER_ELASTICSEARCH_USERNAME", "ELASTICSEARCH_USERNAME"), + Password: envWithDefault("changeme", "FLEET_SERVER_ELASTICSEARCH_PASSWORD", "ELASTICSEARCH_PASSWORD"), + ServiceToken: envWithDefault("", "FLEET_SERVER_SERVICE_TOKEN"), + CA: envWithDefault("", "FLEET_SERVER_ELASTICSEARCH_CA", "ELASTICSEARCH_CA"), + }, + Enable: envBool("FLEET_SERVER_ENABLE"), + Host: envWithDefault("", "FLEET_SERVER_HOST"), + InsecureHTTP: envBool("FLEET_SERVER_INSECURE_HTTP"), + PolicyID: envWithDefault("", "FLEET_SERVER_POLICY_ID", "FLEET_SERVER_POLICY"), + Port: envWithDefault("", "FLEET_SERVER_PORT"), + Headers: envMap("FLEET_HEADER"), + }, + Kibana: kibanaConfig{ + Fleet: kibanaFleetConfig{ + // Remove FLEET_SETUP in 8.x + // The FLEET_SETUP environment variable boolean is a fallback to the old name. The name was updated to + // reflect that its setting up Fleet in Kibana versus setting up Fleet Server. + Setup: envBool("KIBANA_FLEET_SETUP", "FLEET_SETUP"), + Host: envWithDefault("http://kibana:5601", "KIBANA_FLEET_HOST", "KIBANA_HOST"), + Username: envWithDefault("elastic", "KIBANA_FLEET_USERNAME", "KIBANA_USERNAME", "ELASTICSEARCH_USERNAME"), + Password: envWithDefault("changeme", "KIBANA_FLEET_PASSWORD", "KIBANA_PASSWORD", "ELASTICSEARCH_PASSWORD"), + CA: envWithDefault("", "KIBANA_FLEET_CA", "KIBANA_CA", "ELASTICSEARCH_CA"), + }, + RetrySleepDuration: retrySleepDuration, + RetryMaxCount: retryMaxCount, + Headers: envMap("FLEET_KIBANA_HEADER"), + }, + } + return cfg, nil +} diff --git a/x-pack/elastic-agent/pkg/agent/configuration/fleet_server.go b/x-pack/elastic-agent/pkg/agent/configuration/fleet_server.go index e90f4bf3b1c..a87da18ecf2 100644 --- a/x-pack/elastic-agent/pkg/agent/configuration/fleet_server.go +++ b/x-pack/elastic-agent/pkg/agent/configuration/fleet_server.go @@ -40,6 +40,7 @@ type Elasticsearch struct { Password string `config:"password" yaml:"password,omitempty"` ServiceToken string `config:"service_token" yaml:"service_token,omitempty"` TLS *tlscommon.Config `config:"ssl" yaml:"ssl,omitempty"` + Headers map[string]string `config:"headers" yaml:"headers,omitempty"` } // ElasticsearchFromConnStr returns an Elasticsearch configuration from the connection string. diff --git a/x-pack/elastic-agent/pkg/agent/program/program_test.go b/x-pack/elastic-agent/pkg/agent/program/program_test.go index 2691aae9e41..4498f7e5236 100644 --- a/x-pack/elastic-agent/pkg/agent/program/program_test.go +++ b/x-pack/elastic-agent/pkg/agent/program/program_test.go @@ -499,3 +499,9 @@ func (*fakeAgentInfo) Version() string { func (*fakeAgentInfo) Snapshot() bool { return false } + +func (*fakeAgentInfo) Headers() map[string]string { + return map[string]string{ + "h1": "test-header", + } +} diff --git a/x-pack/elastic-agent/pkg/agent/program/supported.go b/x-pack/elastic-agent/pkg/agent/program/supported.go index e53d7dbe26f..bb6cd619177 100644 --- a/x-pack/elastic-agent/pkg/agent/program/supported.go +++ b/x-pack/elastic-agent/pkg/agent/program/supported.go @@ -25,7 +25,7 @@ func init() { // spec/metricbeat.yml // spec/osquerybeat.yml // spec/packetbeat.yml - unpacked := packer.MustUnpack("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") + unpacked := packer.MustUnpack("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") SupportedMap = make(map[string]Spec) for f, v := range unpacked { diff --git a/x-pack/elastic-agent/pkg/agent/transpiler/rules.go b/x-pack/elastic-agent/pkg/agent/transpiler/rules.go index 9135c751d3a..2afb312c930 100644 --- a/x-pack/elastic-agent/pkg/agent/transpiler/rules.go +++ b/x-pack/elastic-agent/pkg/agent/transpiler/rules.go @@ -19,6 +19,7 @@ type AgentInfo interface { AgentID() string Version() string Snapshot() bool + Headers() map[string]string } // RuleList is a container that allow the same tree to be executed on multiple defined Rule. @@ -90,6 +91,8 @@ func (r *RuleList) MarshalYAML() (interface{}, error) { name = "fix_stream" case *InsertDefaultsRule: name = "insert_defaults" + case *InjectHeadersRule: + name = "inject_headers" default: return nil, fmt.Errorf("unknown rule of type %T", rule) } @@ -175,6 +178,8 @@ func (r *RuleList) UnmarshalYAML(unmarshal func(interface{}) error) error { r = &FixStreamRule{} case "insert_defaults": r = &InsertDefaultsRule{} + case "inject_headers": + r = &InjectHeadersRule{} default: return fmt.Errorf("unknown rule of type %s", name) } @@ -1505,6 +1510,70 @@ func InsertDefaults(path string, selectors ...Selector) *InsertDefaultsRule { } } +// InjectHeadersRule injects headers into output. +type InjectHeadersRule struct{} + +// Apply injects headers into output. +func (r *InjectHeadersRule) Apply(agentInfo AgentInfo, ast *AST) (err error) { + defer func() { + if err != nil { + err = errors.New(err, "failed to inject headers into configuration") + } + }() + + headers := agentInfo.Headers() + if len(headers) == 0 { + return nil + } + + outputsNode, found := Lookup(ast, "outputs") + if !found { + return nil + } + + elasticsearchNode, found := outputsNode.Find("elasticsearch") + if found { + headersNode, found := elasticsearchNode.Find("headers") + if found { + headersDict, ok := headersNode.Value().(*Dict) + if !ok { + return errors.New("headers not a dictionary") + } + + for k, v := range headers { + headersDict.value = append(headersDict.value, &Key{ + name: k, + value: &StrVal{value: v}, + }) + } + } else { + nodes := make([]Node, 0, len(headers)) + for k, v := range headers { + nodes = append(nodes, &Key{ + name: k, + value: &StrVal{value: v}, + }) + } + headersDict := NewDict(nodes) + elasticsearchDict, ok := elasticsearchNode.Value().(*Dict) + if !ok { + return errors.New("elasticsearch output is not a dictionary") + } + elasticsearchDict.value = append(elasticsearchDict.value, &Key{ + name: "headers", + value: headersDict, + }) + } + } + + return nil +} + +// InjectHeaders creates a InjectHeadersRule +func InjectHeaders() *InjectHeadersRule { + return &InjectHeadersRule{} +} + // NewRuleList returns a new list of rules to be executed. func NewRuleList(rules ...Rule) *RuleList { return &RuleList{Rules: rules} diff --git a/x-pack/elastic-agent/pkg/agent/transpiler/rules_test.go b/x-pack/elastic-agent/pkg/agent/transpiler/rules_test.go index d6f24a8d8e3..71e12ac444b 100644 --- a/x-pack/elastic-agent/pkg/agent/transpiler/rules_test.go +++ b/x-pack/elastic-agent/pkg/agent/transpiler/rules_test.go @@ -685,6 +685,7 @@ rest: of }, }, }, + "insert defaults into not existing": { givenYAML: ` level_one: @@ -713,6 +714,65 @@ rest: of }, }, }, + + "inject auth headers: no headers": { + givenYAML: ` +outputs: + elasticsearch: + hosts: + - "127.0.0.1:9201" + - "127.0.0.1:9202" + logstash: + port: 5 +`, + expectedYAML: ` +outputs: + elasticsearch: + headers: + h1: test-header + hosts: + - "127.0.0.1:9201" + - "127.0.0.1:9202" + logstash: + port: 5 +`, + rule: &RuleList{ + Rules: []Rule{ + InjectHeaders(), + }, + }, + }, + + "inject auth headers: existing headers": { + givenYAML: ` +outputs: + elasticsearch: + headers: + sample-header: existing + hosts: + - "127.0.0.1:9201" + - "127.0.0.1:9202" + logstash: + port: 5 +`, + expectedYAML: ` +outputs: + elasticsearch: + headers: + sample-header: existing + h1: test-header + hosts: + - "127.0.0.1:9201" + - "127.0.0.1:9202" + logstash: + port: 5 +`, + rule: &RuleList{ + Rules: []Rule{ + InjectHeaders(), + }, + }, + }, } for name, test := range testcases { @@ -777,6 +837,7 @@ func TestSerialization(t *testing.T) { FixStream(), SelectInto("target", "s1", "s2"), InsertDefaults("target", "s1", "s2"), + InjectHeaders(), ) y := `- rename: @@ -847,6 +908,7 @@ func TestSerialization(t *testing.T) { - s1 - s2 path: target +- inject_headers: {} ` t.Run("serialize_rules", func(t *testing.T) { @@ -877,6 +939,12 @@ func (*fakeAgentInfo) Snapshot() bool { return false } +func (*fakeAgentInfo) Headers() map[string]string { + return map[string]string{ + "h1": "test-header", + } +} + func FakeAgentInfo() AgentInfo { return &fakeAgentInfo{} } diff --git a/x-pack/elastic-agent/spec/apm-server.yml b/x-pack/elastic-agent/spec/apm-server.yml index 7e9d2af76dc..01fa6a9fd35 100644 --- a/x-pack/elastic-agent/spec/apm-server.yml +++ b/x-pack/elastic-agent/spec/apm-server.yml @@ -32,4 +32,5 @@ rules: - inputs - output - fleet + - inject_headers: {} when: length(${inputs}) > 0 and hasKey(${output}, 'elasticsearch') diff --git a/x-pack/elastic-agent/spec/filebeat.yml b/x-pack/elastic-agent/spec/filebeat.yml index 0bf33f15e7d..dff5224778c 100644 --- a/x-pack/elastic-agent/spec/filebeat.yml +++ b/x-pack/elastic-agent/spec/filebeat.yml @@ -107,5 +107,8 @@ rules: - filebeat - output - keystore + +- inject_headers: {} + when: length(${filebeat.inputs}) > 0 and hasKey(${output}, 'elasticsearch', 'redis', 'kafka', 'logstash') diff --git a/x-pack/elastic-agent/spec/fleet-server.yml b/x-pack/elastic-agent/spec/fleet-server.yml index 8cb80280842..abb4ad4a502 100644 --- a/x-pack/elastic-agent/spec/fleet-server.yml +++ b/x-pack/elastic-agent/spec/fleet-server.yml @@ -63,4 +63,6 @@ rules: - inputs - output + - inject_headers: {} + when: length(${fleet}) > 0 and length(${inputs}) > 0 and hasKey(${output}, 'elasticsearch') diff --git a/x-pack/elastic-agent/spec/metricbeat.yml b/x-pack/elastic-agent/spec/metricbeat.yml index cb4c1e8ca2b..7653f65fe24 100644 --- a/x-pack/elastic-agent/spec/metricbeat.yml +++ b/x-pack/elastic-agent/spec/metricbeat.yml @@ -96,6 +96,7 @@ rules: - metricbeat - output - keystore +- inject_headers: {} when: length(${metricbeat.modules}) > 0 and hasKey(${output}, 'elasticsearch', 'redis', 'kafka', 'logstash')