Skip to content

Commit

Permalink
feat(exporter/elasticsearch): add Basic Auth support
Browse files Browse the repository at this point in the history
Signed-off-by: hainenber <[email protected]>
  • Loading branch information
hainenber committed Nov 20, 2023
1 parent eac661f commit 294b733
Show file tree
Hide file tree
Showing 4 changed files with 79 additions and 20 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@ internal API changes are not present.
Main (unreleased)
-----------------

### Features

- Add support for Basic Auth-secured connection with Elasticsearch cluster using `prometheus.exporter.elasticsearch`. (@hainenber)

v0.38.0-rc.0 (2023-11-16)
-------------------------

Expand Down
37 changes: 20 additions & 17 deletions component/prometheus/exporter/elasticsearch/elasticsearch.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"time"

"github.com/grafana/agent/component"
commoncfg "github.com/grafana/agent/component/common/config"
"github.com/grafana/agent/component/prometheus/exporter"
"github.com/grafana/agent/pkg/integrations"
"github.com/grafana/agent/pkg/integrations/elasticsearch_exporter"
Expand Down Expand Up @@ -35,23 +36,24 @@ var DefaultArguments = Arguments{
}

type Arguments struct {
Address string `river:"address,attr,optional"`
Timeout time.Duration `river:"timeout,attr,optional"`
AllNodes bool `river:"all,attr,optional"`
Node string `river:"node,attr,optional"`
ExportIndices bool `river:"indices,attr,optional"`
ExportIndicesSettings bool `river:"indices_settings,attr,optional"`
ExportClusterSettings bool `river:"cluster_settings,attr,optional"`
ExportShards bool `river:"shards,attr,optional"`
IncludeAliases bool `river:"aliases,attr,optional"`
ExportSnapshots bool `river:"snapshots,attr,optional"`
ExportClusterInfoInterval time.Duration `river:"clusterinfo_interval,attr,optional"`
CA string `river:"ca,attr,optional"`
ClientPrivateKey string `river:"client_private_key,attr,optional"`
ClientCert string `river:"client_cert,attr,optional"`
InsecureSkipVerify bool `river:"ssl_skip_verify,attr,optional"`
ExportDataStreams bool `river:"data_stream,attr,optional"`
ExportSLM bool `river:"slm,attr,optional"`
Address string `river:"address,attr,optional"`
Timeout time.Duration `river:"timeout,attr,optional"`
AllNodes bool `river:"all,attr,optional"`
Node string `river:"node,attr,optional"`
ExportIndices bool `river:"indices,attr,optional"`
ExportIndicesSettings bool `river:"indices_settings,attr,optional"`
ExportClusterSettings bool `river:"cluster_settings,attr,optional"`
ExportShards bool `river:"shards,attr,optional"`
IncludeAliases bool `river:"aliases,attr,optional"`
ExportSnapshots bool `river:"snapshots,attr,optional"`
ExportClusterInfoInterval time.Duration `river:"clusterinfo_interval,attr,optional"`
CA string `river:"ca,attr,optional"`
ClientPrivateKey string `river:"client_private_key,attr,optional"`
ClientCert string `river:"client_cert,attr,optional"`
InsecureSkipVerify bool `river:"ssl_skip_verify,attr,optional"`
ExportDataStreams bool `river:"data_stream,attr,optional"`
ExportSLM bool `river:"slm,attr,optional"`
BasicAuth *commoncfg.BasicAuth `river:"basic_auth,block,optional"`
}

// SetToDefault implements river.Defaulter.
Expand All @@ -78,5 +80,6 @@ func (a *Arguments) Convert() *elasticsearch_exporter.Config {
InsecureSkipVerify: a.InsecureSkipVerify,
ExportDataStreams: a.ExportDataStreams,
ExportSLM: a.ExportSLM,
BasicAuth: a.BasicAuth,
}
}
18 changes: 18 additions & 0 deletions component/prometheus/exporter/elasticsearch/elasticsearch_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@ import (
"testing"
"time"

commoncfg "github.com/grafana/agent/component/common/config"
"github.com/grafana/agent/pkg/integrations/elasticsearch_exporter"
"github.com/grafana/river"
"github.com/grafana/river/rivertypes"
"github.com/stretchr/testify/require"
)

Expand All @@ -27,6 +29,10 @@ func TestRiverUnmarshal(t *testing.T) {
ssl_skip_verify = true
data_stream = true
slm = true
basic_auth {
username = "username"
password = "pass"
}
`

var args Arguments
Expand All @@ -50,6 +56,10 @@ func TestRiverUnmarshal(t *testing.T) {
InsecureSkipVerify: true,
ExportDataStreams: true,
ExportSLM: true,
BasicAuth: &commoncfg.BasicAuth{
Username: "username",
Password: rivertypes.Secret("pass"),
},
}

require.Equal(t, expected, args)
Expand All @@ -73,6 +83,10 @@ func TestConvert(t *testing.T) {
ssl_skip_verify = true
data_stream = true
slm = true
basic_auth {
username = "username"
password = "pass"
}
`
var args Arguments
err := river.Unmarshal([]byte(riverConfig), &args)
Expand All @@ -97,6 +111,10 @@ func TestConvert(t *testing.T) {
InsecureSkipVerify: true,
ExportDataStreams: true,
ExportSLM: true,
BasicAuth: &commoncfg.BasicAuth{
Username: "username",
Password: rivertypes.Secret("pass"),
},
}
require.Equal(t, expected, *res)
}
40 changes: 37 additions & 3 deletions pkg/integrations/elasticsearch_exporter/elasticsearch_exporter.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,17 @@ package elasticsearch_exporter //nolint:golint

import (
"context"
"encoding/base64"
"fmt"
"net/http"
"net/url"
"os"
"strings"
"time"

"github.com/go-kit/log"
"github.com/go-kit/log/level"
commoncfg "github.com/grafana/agent/component/common/config"
"github.com/grafana/agent/pkg/integrations"
integrations_v2 "github.com/grafana/agent/pkg/integrations/v2"
"github.com/grafana/agent/pkg/integrations/v2/metricsutils"
Expand Down Expand Up @@ -66,6 +70,19 @@ type Config struct {
ExportDataStreams bool `yaml:"data_stream,omitempty"`
// Export stats for Snapshot Lifecycle Management
ExportSLM bool `yaml:"slm,omitempty"`
// BasicAuth block allows secure connection with Elasticsearch cluster via Basic-Auth
BasicAuth *commoncfg.BasicAuth `yaml:"basic_auth,omitempty"`
}

// Custom http.Transport struct for Basic Auth-secured communication with ES cluster
type BasicAuthHTTPTransport struct {
http.Transport
authHeader string
}

func (b *BasicAuthHTTPTransport) RoundTrip(req *http.Request) (*http.Response, error) {
req.Header.Add("authorization", b.authHeader)
return b.Transport.RoundTrip(req)
}

// UnmarshalYAML implements yaml.Unmarshaler for Config
Expand Down Expand Up @@ -115,14 +132,31 @@ func New(logger log.Logger, c *Config) (integrations.Integration, error) {
// returns nil if not provided and falls back to simple TCP.
tlsConfig := createTLSConfig(c.CA, c.ClientCert, c.ClientPrivateKey, c.InsecureSkipVerify)

httpClient := &http.Client{
Timeout: c.Timeout,
Transport: &http.Transport{
esHttpTransport := &BasicAuthHTTPTransport{
Transport: http.Transport{
TLSClientConfig: tlsConfig,
Proxy: http.ProxyFromEnvironment,
},
}

if c.BasicAuth != nil {
password := string(c.BasicAuth.Password)
if len(c.BasicAuth.PasswordFile) > 0 {
buff, err := os.ReadFile(c.BasicAuth.PasswordFile)
if err != nil {
return nil, fmt.Errorf("unable to load password file %s: %w", c.BasicAuth.PasswordFile, err)
}
password = strings.TrimSpace(string(buff))
}
encodedAuth := base64.StdEncoding.EncodeToString([]byte(c.BasicAuth.Username + ":" + password))
esHttpTransport.authHeader = "Basic " + encodedAuth
}

httpClient := &http.Client{
Timeout: c.Timeout,
Transport: esHttpTransport,
}

clusterInfoRetriever := clusterinfo.New(logger, httpClient, esURL, c.ExportClusterInfoInterval)

collectors := []prometheus.Collector{
Expand Down

0 comments on commit 294b733

Please sign in to comment.