From a13b139ed73e3e25a4e36f4a718eb8b13b430fb7 Mon Sep 17 00:00:00 2001 From: michel-laterman Date: Fri, 20 Aug 2021 15:07:41 -0700 Subject: [PATCH 1/6] Allow Kibana client to authorize with Elasticsearch API key Allow the libbeat/kibana client to authorize using an API key instead of a username/password. This setting can be specified under output.elasticsearch.api_key in the same way that the username/password can be. --- CHANGELOG.next.asciidoc | 1 + libbeat/cmd/instance/beat.go | 4 ++++ libbeat/kibana/client.go | 15 +++++++++++++++ libbeat/kibana/client_config.go | 12 ++++++++++++ 4 files changed, 32 insertions(+) diff --git a/CHANGELOG.next.asciidoc b/CHANGELOG.next.asciidoc index 70aa6d864ce..575c3259b4b 100644 --- a/CHANGELOG.next.asciidoc +++ b/CHANGELOG.next.asciidoc @@ -200,6 +200,7 @@ https://github.com/elastic/beats/compare/v7.0.0-alpha2...master[Check the HEAD d - Preserve annotations in a kubernetes namespace metadata {pull}27045[27045] - Allow conditional processing in `decode_xml` and `decode_xml_wineventlog`. {pull}27159[27159] - Fix build constraint that caused issues with doc builds. {pull}27381[27381] +- Beat `setup kibana` command may use the elasticsearch API key defined in `output.elasticsearch.api_key`. {issue}24015[24015] *Auditbeat* diff --git a/libbeat/cmd/instance/beat.go b/libbeat/cmd/instance/beat.go index ade05d78f8b..bcd1c315036 100644 --- a/libbeat/cmd/instance/beat.go +++ b/libbeat/cmd/instance/beat.go @@ -1085,6 +1085,7 @@ func InitKibanaConfig(beatConfig beatConfig) *common.Config { if esConfig.Enabled() { username, _ := esConfig.String("username", -1) password, _ := esConfig.String("password", -1) + api_key, _ := esConfig.String("api_key", -1) if !kibanaConfig.HasField("username") && username != "" { kibanaConfig.SetString("username", -1, username) @@ -1092,6 +1093,9 @@ func InitKibanaConfig(beatConfig beatConfig) *common.Config { if !kibanaConfig.HasField("password") && password != "" { kibanaConfig.SetString("password", -1, password) } + if !kibanaConfig.HasField("api_key") && api_key != "" { + kibanaConfig.SetString("api_key", -1, api_key) + } } return kibanaConfig } diff --git a/libbeat/kibana/client.go b/libbeat/kibana/client.go index 2d2b9dc313a..a96464f2ccc 100644 --- a/libbeat/kibana/client.go +++ b/libbeat/kibana/client.go @@ -20,6 +20,7 @@ package kibana import ( "bytes" "context" + "encoding/base64" "encoding/json" "fmt" "io" @@ -48,6 +49,7 @@ type Connection struct { URL string Username string Password string + APIKey string Headers http.Header HTTP *http.Client @@ -109,6 +111,10 @@ func NewClientWithConfig(config *ClientConfig) (*Client, error) { // NewClientWithConfig creates and returns a kibana client using the given config func NewClientWithConfigDefault(config *ClientConfig, defaultPort int) (*Client, error) { + if err := config.Validate(); err != nil { + return nil, err + } + p := config.Path if config.SpaceID != "" { p = path.Join(p, "s", config.SpaceID) @@ -131,6 +137,10 @@ func NewClientWithConfigDefault(config *ClientConfig, defaultPort int) (*Client, password, _ = u.User.Password() u.User = nil + if config.APIKey != "" && (username != "" || password != "") { + return nil, fmt.Errorf("cannot set api_key with username/password in Kibana URL") + } + // Re-write URL without credentials. kibanaURL = u.String() } @@ -153,6 +163,7 @@ func NewClientWithConfigDefault(config *ClientConfig, defaultPort int) (*Client, URL: kibanaURL, Username: username, Password: password, + APIKey: config.APIKey, Headers: headers, HTTP: rt, }, @@ -212,6 +223,10 @@ func (conn *Connection) SendWithContext(ctx context.Context, method, extraPath s if conn.Username != "" || conn.Password != "" { req.SetBasicAuth(conn.Username, conn.Password) } + if conn.APIKey != "" { + v := fmt.Sprintf("ApiKey %s", base64.StdEncoding.EncodeToString([]byte(conn.APIKey))) + 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 a10a8031242..6219fb3c718 100644 --- a/libbeat/kibana/client_config.go +++ b/libbeat/kibana/client_config.go @@ -18,6 +18,8 @@ package kibana import ( + "fmt" + "github.com/elastic/beats/v7/libbeat/common/transport/httpcommon" ) @@ -29,6 +31,7 @@ type ClientConfig struct { 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"` // Headers holds headers to include in every request sent to Kibana. Headers map[string]string `config:"headers" yaml:"headers,omitempty"` @@ -47,6 +50,15 @@ func DefaultClientConfig() ClientConfig { SpaceID: "", Username: "", Password: "", + APIKey: "", Transport: httpcommon.DefaultHTTPTransportSettings(), } } + +func (c *ClientConfig) Validate() error { + if c.APIKey != "" && (c.Username != "" || c.Password != "") { + return fmt.Errorf("cannot set both api_key and username/password") + } + + return nil +} From d8504897a1ffbed7bdb7c5d6656840b51f19fc5c Mon Sep 17 00:00:00 2001 From: michel-laterman Date: Fri, 20 Aug 2021 15:19:14 -0700 Subject: [PATCH 2/6] Add PR number to CHANGELOG --- CHANGELOG.next.asciidoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.next.asciidoc b/CHANGELOG.next.asciidoc index 575c3259b4b..e2608769546 100644 --- a/CHANGELOG.next.asciidoc +++ b/CHANGELOG.next.asciidoc @@ -200,7 +200,7 @@ https://github.com/elastic/beats/compare/v7.0.0-alpha2...master[Check the HEAD d - Preserve annotations in a kubernetes namespace metadata {pull}27045[27045] - Allow conditional processing in `decode_xml` and `decode_xml_wineventlog`. {pull}27159[27159] - Fix build constraint that caused issues with doc builds. {pull}27381[27381] -- Beat `setup kibana` command may use the elasticsearch API key defined in `output.elasticsearch.api_key`. {issue}24015[24015] +- Beat `setup kibana` command may use the elasticsearch API key defined in `output.elasticsearch.api_key`. {issue}24015[24015] {pull}27540[27540] *Auditbeat* From ba0168086ab4a266d0bda19afc412874b5a0b1ea Mon Sep 17 00:00:00 2001 From: michel-laterman Date: Mon, 23 Aug 2021 13:38:33 -0700 Subject: [PATCH 3/6] Add testing --- libbeat/cmd/instance/beat_test.go | 2 ++ libbeat/cmd/test/filebeat_test.yml | 1 + libbeat/kibana/client_config_test.go | 53 ++++++++++++++++++++++++++++ 3 files changed, 56 insertions(+) create mode 100644 libbeat/kibana/client_config_test.go diff --git a/libbeat/cmd/instance/beat_test.go b/libbeat/cmd/instance/beat_test.go index 86ae2697d10..bb541c3d204 100644 --- a/libbeat/cmd/instance/beat_test.go +++ b/libbeat/cmd/instance/beat_test.go @@ -85,11 +85,13 @@ func TestInitKibanaConfig(t *testing.T) { kibanaConfig := InitKibanaConfig(b.Config) username, err := kibanaConfig.String("username", -1) password, err := kibanaConfig.String("password", -1) + api_key, err := kibanaConfig.String("api_key", -1) protocol, err := kibanaConfig.String("protocol", -1) host, err := kibanaConfig.String("host", -1) assert.Equal(t, "elastic-test-username", username) assert.Equal(t, "elastic-test-password", password) + assert.Equal(t, "elastic-test-api-key", api_key) assert.Equal(t, "https", protocol) assert.Equal(t, "127.0.0.1:5601", host) } diff --git a/libbeat/cmd/test/filebeat_test.yml b/libbeat/cmd/test/filebeat_test.yml index 5376601942b..fd3cb3b7ced 100644 --- a/libbeat/cmd/test/filebeat_test.yml +++ b/libbeat/cmd/test/filebeat_test.yml @@ -23,4 +23,5 @@ output.elasticsearch: # Optional protocol and basic auth credentials. username: "elastic-test-username" password: "elastic-test-password" + api_key: "elastic-test-api-key" protocal: "https" diff --git a/libbeat/kibana/client_config_test.go b/libbeat/kibana/client_config_test.go new file mode 100644 index 00000000000..0594af398e0 --- /dev/null +++ b/libbeat/kibana/client_config_test.go @@ -0,0 +1,53 @@ +package kibana + +import ( + "fmt" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestClientConfigValdiate(t *testing.T) { + tests := []struct { + name string + c *ClientConfig + err error + }{{ + name: "empty params", + c: &ClientConfig{}, + err: nil, + }, { + name: "username and password", + c: &ClientConfig{ + Username: "user", + Password: "pass", + }, + err: nil, + }, { + name: "api_key", + c: &ClientConfig{ + APIKey: "api-key", + }, + err: nil, + }, { + name: "username and api_key", + c: &ClientConfig{ + Username: "user", + APIKey: "apiKey", + }, + err: fmt.Errorf("cannot set both api_key and username/password"), + }} + + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + err := tt.c.Validate() + if tt.err == nil { + assert.Nil(t, err) + } else { + assert.EqualError(t, err, tt.err.Error()) + } + }) + } + +} From c09c273e8ee6fa007ed3c0805dece7e906e99b74 Mon Sep 17 00:00:00 2001 From: michel-laterman Date: Tue, 24 Aug 2021 09:32:23 -0700 Subject: [PATCH 4/6] Add license headers --- libbeat/kibana/client_config_test.go | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/libbeat/kibana/client_config_test.go b/libbeat/kibana/client_config_test.go index 0594af398e0..1355e696aad 100644 --- a/libbeat/kibana/client_config_test.go +++ b/libbeat/kibana/client_config_test.go @@ -1,3 +1,20 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + package kibana import ( From 676e11a7f276ed0a3d67f205d72783023dc6100c Mon Sep 17 00:00:00 2001 From: Michel Laterman <82832767+michel-laterman@users.noreply.github.com> Date: Mon, 30 Aug 2021 07:19:33 -0700 Subject: [PATCH 5/6] Update libbeat/kibana/client.go Co-authored-by: Andrew Kroh --- libbeat/kibana/client.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libbeat/kibana/client.go b/libbeat/kibana/client.go index a96464f2ccc..53f07fd0317 100644 --- a/libbeat/kibana/client.go +++ b/libbeat/kibana/client.go @@ -224,7 +224,7 @@ func (conn *Connection) SendWithContext(ctx context.Context, method, extraPath s req.SetBasicAuth(conn.Username, conn.Password) } if conn.APIKey != "" { - v := fmt.Sprintf("ApiKey %s", base64.StdEncoding.EncodeToString([]byte(conn.APIKey))) + v := "ApiKey "+base64.StdEncoding.EncodeToString([]byte(conn.APIKey)) req.Header.Set("Authorization", v) } From 94bbde09511d5d8bcf01a1e7bfbb7699163a4bbb Mon Sep 17 00:00:00 2001 From: michel-laterman Date: Mon, 30 Aug 2021 08:40:49 -0700 Subject: [PATCH 6/6] gofmt --- libbeat/kibana/client.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libbeat/kibana/client.go b/libbeat/kibana/client.go index 53f07fd0317..dd6c3e3d667 100644 --- a/libbeat/kibana/client.go +++ b/libbeat/kibana/client.go @@ -224,7 +224,7 @@ func (conn *Connection) SendWithContext(ctx context.Context, method, extraPath s req.SetBasicAuth(conn.Username, conn.Password) } if conn.APIKey != "" { - v := "ApiKey "+base64.StdEncoding.EncodeToString([]byte(conn.APIKey)) + v := "ApiKey " + base64.StdEncoding.EncodeToString([]byte(conn.APIKey)) req.Header.Set("Authorization", v) }