Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow Kibana client to authorize with Elasticsearch API key #27540

Merged
merged 7 commits into from
Aug 30, 2021
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.next.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,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 @@ -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)))
michel-laterman marked this conversation as resolved.
Show resolved Hide resolved
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())
}
})
}

}