diff --git a/x-pack/elastic-agent/CHANGELOG.next.asciidoc b/x-pack/elastic-agent/CHANGELOG.next.asciidoc index ffd8febf29c..61bcfde5a52 100644 --- a/x-pack/elastic-agent/CHANGELOG.next.asciidoc +++ b/x-pack/elastic-agent/CHANGELOG.next.asciidoc @@ -116,5 +116,6 @@ - Enable agent to send custom headers to kibana/ES {pull}26275[26275] - Set `agent.id` to the Fleet Agent ID in events published from inputs backed by Beats. {issue}21121[21121] {pull}26394[26394] - Add proxy support to artifact downloader and communication with fleet server. {pull}25219[25219] +- Add proxy support to enroll command. {pull}26514[26514] - Enable configuring monitoring namespace {issue}26439[26439] - Communicate with Fleet Server over HTTP2. {pull}26474[26474] diff --git a/x-pack/elastic-agent/pkg/agent/cmd/enroll.go b/x-pack/elastic-agent/pkg/agent/cmd/enroll.go index 2828bc4f0da..ab5edcf8e0f 100644 --- a/x-pack/elastic-agent/pkg/agent/cmd/enroll.go +++ b/x-pack/elastic-agent/pkg/agent/cmd/enroll.go @@ -66,6 +66,9 @@ func addEnrollFlags(cmd *cobra.Command) { cmd.Flags().StringP("ca-sha256", "p", "", "Comma separated list of certificate authorities hash pins used for certificate verifications") cmd.Flags().BoolP("insecure", "i", false, "Allow insecure connection to fleet-server") cmd.Flags().StringP("staging", "", "", "Configures agent to download artifacts from a staging build") + cmd.Flags().StringP("proxy-url", "", "", "Configures the proxy url") + cmd.Flags().BoolP("proxy-disabled", "", false, "Disable proxy support including environment variables") + cmd.Flags().StringSliceP("proxy-header", "", []string{}, "Proxy headers used with CONNECT request") } func buildEnrollmentFlags(cmd *cobra.Command, url string, token string) []string { @@ -89,6 +92,9 @@ func buildEnrollmentFlags(cmd *cobra.Command, url string, token string) []string sha256, _ := cmd.Flags().GetString("ca-sha256") insecure, _ := cmd.Flags().GetBool("insecure") staging, _ := cmd.Flags().GetString("staging") + fProxyURL, _ := cmd.Flags().GetString("proxy-url") + fProxyDisabled, _ := cmd.Flags().GetBool("proxy-disabled") + fProxyHeaders, _ := cmd.Flags().GetStringSlice("proxy-header") args := []string{} if url != "" { @@ -155,6 +161,20 @@ func buildEnrollmentFlags(cmd *cobra.Command, url string, token string) []string args = append(args, "--staging") args = append(args, staging) } + + if fProxyURL != "" { + args = append(args, "--proxy-url") + args = append(args, fProxyURL) + } + if fProxyDisabled { + args = append(args, "--proxy-disabled") + args = append(args, "true") + } + for k, v := range mapFromEnvList(fProxyHeaders) { + args = append(args, "--proxy-header") + args = append(args, k+"="+v) + } + return args } @@ -228,6 +248,9 @@ func enroll(streams *cli.IOStreams, cmd *cobra.Command, args []string) error { fCert, _ := cmd.Flags().GetString("fleet-server-cert") fCertKey, _ := cmd.Flags().GetString("fleet-server-cert-key") fInsecure, _ := cmd.Flags().GetBool("fleet-server-insecure-http") + fProxyURL, _ := cmd.Flags().GetString("proxy-url") + fProxyDisabled, _ := cmd.Flags().GetBool("proxy-disabled") + fProxyHeaders, _ := cmd.Flags().GetStringSlice("proxy-header") caStr, _ := cmd.Flags().GetString("certificate-authorities") CAs := cli.StringToSlice(caStr) @@ -257,6 +280,9 @@ func enroll(streams *cli.IOStreams, cmd *cobra.Command, args []string) error { Insecure: fInsecure, SpawnAgent: !fromInstall, Headers: mapFromEnvList(fHeaders), + ProxyURL: fProxyURL, + ProxyDisabled: fProxyDisabled, + ProxyHeaders: mapFromEnvList(fProxyHeaders), }, } 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 cbc5f209023..8a1a1563bfc 100644 --- a/x-pack/elastic-agent/pkg/agent/cmd/enroll_cmd.go +++ b/x-pack/elastic-agent/pkg/agent/cmd/enroll_cmd.go @@ -10,14 +10,18 @@ import ( "fmt" "io" "math/rand" + "net/http" + "net/url" "os" "os/exec" "time" "gopkg.in/yaml.v2" + "github.com/elastic/beats/v7/libbeat/common" "github.com/elastic/beats/v7/libbeat/common/backoff" + "github.com/elastic/beats/v7/libbeat/common/transport/httpcommon" "github.com/elastic/beats/v7/libbeat/common/transport/tlscommon" "github.com/elastic/beats/v7/x-pack/elastic-agent/pkg/agent/application" "github.com/elastic/beats/v7/x-pack/elastic-agent/pkg/agent/application/filelock" @@ -80,6 +84,9 @@ type enrollCmdFleetServerOption struct { Insecure bool SpawnAgent bool Headers map[string]string + ProxyURL string + ProxyDisabled bool + ProxyHeaders map[string]string } // enrollCmdOption define all the supported enrollment option. @@ -116,6 +123,29 @@ func (e *enrollCmdOption) remoteConfig() (remote.Config, error) { } cfg.Transport.TLS = &tlsCfg + + var proxyURL *url.URL + if e.FleetServer.ProxyURL != "" { + proxyURL, err = common.ParseURL(e.FleetServer.ProxyURL) + if err != nil { + return remote.Config{}, err + } + } + + var headers http.Header + if len(e.FleetServer.ProxyHeaders) > 0 { + headers = http.Header{} + for k, v := range e.FleetServer.ProxyHeaders { + headers.Add(k, v) + } + } + + cfg.Transport.Proxy = httpcommon.HTTPClientProxySettings{ + URL: proxyURL, + Disable: e.FleetServer.ProxyDisabled, + Headers: headers, + } + return cfg, nil } @@ -166,15 +196,10 @@ func (c *enrollCmd) Execute(ctx context.Context) error { return err } - if c.options.FleetServer.ConnStr != "" { - token, err := c.fleetServerBootstrap(ctx) - if err != nil { - return err - } - if c.options.EnrollAPIKey == "" && token != "" { - c.options.EnrollAPIKey = token - } - } + // localFleetServer indicates that we start our internal fleet server. Agent + // will communicate to the internal fleet server on localhost only. + // Connection setup should disable proxies in that case. + localFleetServer := c.options.FleetServer.ConnStr != "" c.remoteConfig, err = c.options.remoteConfig() if err != nil { @@ -184,6 +209,20 @@ func (c *enrollCmd) Execute(ctx context.Context) error { errors.M(errors.MetaKeyURI, c.options.URL)) } + if localFleetServer { + // Ensure that the agent does not use a proxy configuration + // when connecting to the local fleet server. + c.remoteConfig.Transport.Proxy.Disable = true + + token, err := c.fleetServerBootstrap(ctx) + if err != nil { + return err + } + if c.options.EnrollAPIKey == "" && token != "" { + c.options.EnrollAPIKey = token + } + } + c.client, err = fleetclient.NewWithConfig(c.log, c.remoteConfig) if err != nil { return errors.New( @@ -234,7 +273,11 @@ func (c *enrollCmd) fleetServerBootstrap(ctx context.Context) (string, error) { 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.Headers) + c.options.FleetServer.Headers, + c.options.FleetServer.ProxyURL, + c.options.FleetServer.ProxyDisabled, + c.options.FleetServer.ProxyHeaders, + ) if err != nil { return "", err } @@ -419,13 +462,15 @@ func (c *enrollCmd) enroll(ctx context.Context, persistentConfig map[string]inte return err } - if c.options.FleetServer.ConnStr != "" { + localFleetServer := c.options.FleetServer.ConnStr != "" + if localFleetServer { serverConfig, err := createFleetServerBootstrapConfig( 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.Headers) + c.options.FleetServer.Headers, + c.options.FleetServer.ProxyURL, c.options.FleetServer.ProxyDisabled, c.options.FleetServer.ProxyHeaders) if err != nil { return err } @@ -725,7 +770,12 @@ func createFleetServerBootstrapConfig( port uint16, cert, key, esCA string, headers map[string]string, + proxyURL string, + proxyDisabled bool, + proxyHeaders map[string]string, ) (*configuration.FleetAgentConfig, error) { + localFleetServer := connStr != "" + es, err := configuration.ElasticsearchFromConnStr(connStr, serviceToken) if err != nil { return nil, err @@ -750,6 +800,10 @@ func createFleetServerBootstrapConfig( es.Headers[k] = v } } + es.ProxyURL = proxyURL + es.ProxyDisabled = proxyDisabled + es.ProxyHeaders = proxyHeaders + cfg := configuration.DefaultFleetAgentConfig() cfg.Enabled = true cfg.Server = &configuration.FleetServerConfig{ @@ -772,6 +826,10 @@ func createFleetServerBootstrapConfig( } } + if localFleetServer { + cfg.Client.Transport.Proxy.Disable = true + } + if err := cfg.Valid(); err != nil { return nil, errors.New(err, "invalid enrollment options", errors.TypeConfig) } 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 a87da18ecf2..e25c5815584 100644 --- a/x-pack/elastic-agent/pkg/agent/configuration/fleet_server.go +++ b/x-pack/elastic-agent/pkg/agent/configuration/fleet_server.go @@ -33,14 +33,17 @@ type FleetServerOutputConfig struct { // Elasticsearch is the configuration for elasticsearch. type Elasticsearch struct { - Protocol string `config:"protocol" yaml:"protocol"` - Hosts []string `config:"hosts" yaml:"hosts"` - Path string `config:"path" yaml:"path,omitempty"` - Username string `config:"username" yaml:"username,omitempty"` - 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"` + Protocol string `config:"protocol" yaml:"protocol"` + Hosts []string `config:"hosts" yaml:"hosts"` + Path string `config:"path" yaml:"path,omitempty"` + Username string `config:"username" yaml:"username,omitempty"` + 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"` + ProxyURL string `config:"proxy_url" yaml:"proxy_url,omitempty"` + ProxyDisabled bool `config:"proxy_disabled" yaml:"proxy_disabled"` + ProxyHeaders map[string]string `config:"proxy_headers" yaml:"proxy_headers"` } // ElasticsearchFromConnStr returns an Elasticsearch configuration from the connection string.