Skip to content

Commit

Permalink
Allow Kibana client to authorize with Elasticsearch API key (elastic#…
Browse files Browse the repository at this point in the history
…27540)

* 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.

* Add PR number to CHANGELOG

* Add testing

* Add license headers

* Update libbeat/kibana/client.go

Co-authored-by: Andrew Kroh <andrew.kroh@elastic.co>

* gofmt

Co-authored-by: Andrew Kroh <andrew.kroh@elastic.co>
  • Loading branch information
michel-laterman and andrewkroh authored Aug 30, 2021
1 parent 02315d9 commit 0976134
Show file tree
Hide file tree
Showing 7 changed files with 105 additions and 0 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.next.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,7 @@ https://github.com/elastic/beats/compare/v7.0.0-alpha2...master[Check the HEAD d
- 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]
- Do not try to load ILM policy if `check_exists` is `false`. {pull}27508[27508] {issue}26322[26322]
- Beat `setup kibana` command may use the elasticsearch API key defined in `output.elasticsearch.api_key`. {issue}24015[24015] {pull}27540[27540]

*Auditbeat*

Expand Down
4 changes: 4 additions & 0 deletions libbeat/cmd/instance/beat.go
Original file line number Diff line number Diff line change
Expand Up @@ -1085,13 +1085,17 @@ 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)
}
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
}
Expand Down
2 changes: 2 additions & 0 deletions libbeat/cmd/instance/beat_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand Down
1 change: 1 addition & 0 deletions libbeat/cmd/test/filebeat_test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
15 changes: 15 additions & 0 deletions libbeat/kibana/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ package kibana
import (
"bytes"
"context"
"encoding/base64"
"encoding/json"
"fmt"
"io"
Expand Down Expand Up @@ -48,6 +49,7 @@ type Connection struct {
URL string
Username string
Password string
APIKey string
Headers http.Header

HTTP *http.Client
Expand Down Expand Up @@ -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)
Expand All @@ -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()
}
Expand All @@ -153,6 +163,7 @@ func NewClientWithConfigDefault(config *ClientConfig, defaultPort int) (*Client,
URL: kibanaURL,
Username: username,
Password: password,
APIKey: config.APIKey,
Headers: headers,
HTTP: rt,
},
Expand Down Expand Up @@ -214,6 +225,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 := "ApiKey " + base64.StdEncoding.EncodeToString([]byte(conn.APIKey))
req.Header.Set("Authorization", v)
}

addHeaders(req.Header, conn.Headers)
addHeaders(req.Header, headers)
Expand Down
12 changes: 12 additions & 0 deletions libbeat/kibana/client_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@
package kibana

import (
"fmt"

"github.com/elastic/beats/v7/libbeat/common/transport/httpcommon"
)

Expand All @@ -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"`
Expand All @@ -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
}
70 changes: 70 additions & 0 deletions libbeat/kibana/client_config_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
// 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 (
"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())
}
})
}

}

0 comments on commit 0976134

Please sign in to comment.