diff --git a/libbeat/kibana/client.go b/libbeat/kibana/client.go index 4a96546dee2..111db2e3816 100644 --- a/libbeat/kibana/client.go +++ b/libbeat/kibana/client.go @@ -48,11 +48,12 @@ var ( ) type Connection struct { - URL string - Username string - Password string - APIKey string - Headers http.Header + URL string + Username string + Password string + APIKey string + ServiceToken string + Headers http.Header HTTP *http.Client Version common.Version @@ -196,12 +197,13 @@ func NewClientWithConfigDefault(config *ClientConfig, defaultPort int, beatname client := &Client{ Connection: Connection{ - URL: kibanaURL, - Username: username, - Password: password, - APIKey: config.APIKey, - Headers: headers, - HTTP: rt, + URL: kibanaURL, + Username: username, + Password: password, + APIKey: config.APIKey, + ServiceToken: config.ServiceToken, + Headers: headers, + HTTP: rt, }, log: log, } @@ -267,6 +269,10 @@ func (conn *Connection) SendWithContext(ctx context.Context, method, extraPath s v := "ApiKey " + base64.StdEncoding.EncodeToString([]byte(conn.APIKey)) req.Header.Set("Authorization", v) } + if conn.ServiceToken != "" { + v := "Bearer " + conn.ServiceToken + req.Header.Set("Authorization", v) + } addHeaders(req.Header, conn.Headers) addHeaders(req.Header, headers) diff --git a/libbeat/kibana/client_config.go b/libbeat/kibana/client_config.go index 6219fb3c718..c8c3758c71b 100644 --- a/libbeat/kibana/client_config.go +++ b/libbeat/kibana/client_config.go @@ -25,13 +25,14 @@ import ( // ClientConfig to connect to Kibana type ClientConfig struct { - Protocol string `config:"protocol" yaml:"protocol,omitempty"` - Host string `config:"host" yaml:"host,omitempty"` - Path string `config:"path" yaml:"path,omitempty"` - SpaceID string `config:"space.id" yaml:"space.id,omitempty"` - Username string `config:"username" yaml:"username,omitempty"` - Password string `config:"password" yaml:"password,omitempty"` - APIKey string `config:"api_key" yaml:"api_key,omitempty"` + Protocol string `config:"protocol" yaml:"protocol,omitempty"` + Host string `config:"host" yaml:"host,omitempty"` + Path string `config:"path" yaml:"path,omitempty"` + SpaceID string `config:"space.id" yaml:"space.id,omitempty"` + Username string `config:"username" yaml:"username,omitempty"` + Password string `config:"password" yaml:"password,omitempty"` + APIKey string `config:"api_key" yaml:"api_key,omitempty"` + ServiceToken string `config:"service_token" yaml:"service_token,omitempty"` // Headers holds headers to include in every request sent to Kibana. Headers map[string]string `config:"headers" yaml:"headers,omitempty"` @@ -44,14 +45,15 @@ type ClientConfig struct { // DefaultClientConfig connects to a locally running kibana over HTTP func DefaultClientConfig() ClientConfig { return ClientConfig{ - Protocol: "http", - Host: "localhost:5601", - Path: "", - SpaceID: "", - Username: "", - Password: "", - APIKey: "", - Transport: httpcommon.DefaultHTTPTransportSettings(), + Protocol: "http", + Host: "localhost:5601", + Path: "", + SpaceID: "", + Username: "", + Password: "", + APIKey: "", + ServiceToken: "", + Transport: httpcommon.DefaultHTTPTransportSettings(), } } diff --git a/libbeat/kibana/client_config_test.go b/libbeat/kibana/client_config_test.go index 1355e696aad..0e365644341 100644 --- a/libbeat/kibana/client_config_test.go +++ b/libbeat/kibana/client_config_test.go @@ -46,6 +46,12 @@ func TestClientConfigValdiate(t *testing.T) { APIKey: "api-key", }, err: nil, + }, { + name: "service_token", + c: &ClientConfig{ + ServiceToken: "service_token", + }, + err: nil, }, { name: "username and api_key", c: &ClientConfig{ diff --git a/libbeat/kibana/client_test.go b/libbeat/kibana/client_test.go index 27dfc53f370..d3e2a66f8d3 100644 --- a/libbeat/kibana/client_test.go +++ b/libbeat/kibana/client_test.go @@ -96,6 +96,26 @@ func TestSuccess(t *testing.T) { assert.NoError(t, err) } +func TestServiceToken(t *testing.T) { + serviceToken := "fakeservicetoken" + + kibanaTs := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Write([]byte(`{}`)) + + assert.Equal(t, "Bearer "+serviceToken, r.Header.Get("Authorization")) + })) + defer kibanaTs.Close() + + conn := Connection{ + URL: kibanaTs.URL, + HTTP: http.DefaultClient, + ServiceToken: serviceToken, + } + code, _, err := conn.Request(http.MethodPost, "", url.Values{}, http.Header{"foo": []string{"bar"}}, nil) + assert.Equal(t, http.StatusOK, code) + assert.NoError(t, err) +} + func TestNewKibanaClient(t *testing.T) { var requests []*http.Request kibanaTs := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { diff --git a/x-pack/elastic-agent/CHANGELOG.next.asciidoc b/x-pack/elastic-agent/CHANGELOG.next.asciidoc index e51e9f1ed66..95f7b90e89b 100644 --- a/x-pack/elastic-agent/CHANGELOG.next.asciidoc +++ b/x-pack/elastic-agent/CHANGELOG.next.asciidoc @@ -145,3 +145,4 @@ - Agent now adapts the beats queue size based on output settings. {issue}26638[26638] {pull}27429[27429] - Support ephemeral containers in Kubernetes dynamic provider. {issue}27020[#27020] {pull}27707[27707] - Add complete k8s metadata through composable provider. {pull}27691[27691] +- Add `KIBANA_FLEET_SERVICE_TOKEN` to Elastic Agent container. {pull}28096[28096] diff --git a/x-pack/elastic-agent/pkg/agent/cmd/container.go b/x-pack/elastic-agent/pkg/agent/cmd/container.go index 5faba8548c4..8d0a4bb4287 100644 --- a/x-pack/elastic-agent/pkg/agent/cmd/container.go +++ b/x-pack/elastic-agent/pkg/agent/cmd/container.go @@ -471,6 +471,7 @@ func kibanaClient(cfg kibanaConfig, headers map[string]string) (*kibana.Client, Host: cfg.Fleet.Host, Username: cfg.Fleet.Username, Password: cfg.Fleet.Password, + ServiceToken: cfg.Fleet.ServiceToken, IgnoreVersion: true, Transport: transport, Headers: headers, diff --git a/x-pack/elastic-agent/pkg/agent/cmd/setup_config.go b/x-pack/elastic-agent/pkg/agent/cmd/setup_config.go index 4330c967e9f..f0076af5435 100644 --- a/x-pack/elastic-agent/pkg/agent/cmd/setup_config.go +++ b/x-pack/elastic-agent/pkg/agent/cmd/setup_config.go @@ -53,11 +53,12 @@ type kibanaConfig struct { } type kibanaFleetConfig struct { - CA string `config:"ca"` - Host string `config:"host"` - Password string `config:"password"` - Setup bool `config:"setup"` - Username string `config:"username"` + CA string `config:"ca"` + Host string `config:"host"` + Password string `config:"password"` + Setup bool `config:"setup"` + Username string `config:"username"` + ServiceToken string `config:"service_token"` } func defaultAccessConfig() (setupConfig, error) { @@ -104,11 +105,12 @@ func defaultAccessConfig() (setupConfig, error) { // 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"), + 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"), + ServiceToken: envWithDefault("", "KIBANA_FLEET_SERVICE_TOKEN", "FLEET_SERVER_SERVICE_TOKEN"), + CA: envWithDefault("", "KIBANA_FLEET_CA", "KIBANA_CA", "ELASTICSEARCH_CA"), }, RetrySleepDuration: retrySleepDuration, RetryMaxCount: retryMaxCount,