Skip to content

Commit

Permalink
Add bearer_token_file paramter to HTTP helper (elastic#7527)
Browse files Browse the repository at this point in the history
This change allows to load bearer tokens from files in modules using
the HTTP helper. This is especially useful for Kubernetes and Prometheus, as some deployments enforce SSL access (like OpenShift):

```
- module: kubernetes
  metricsets:
    - pod
  bearer_token_file: /var/run/secrets/kubernetes.io/serviceaccount/token
  ssl.certificate_authorities:
    - /var/run/secrets/kubernetes.io/serviceaccount/service-ca.crt
```

Closes elastic#7518
  • Loading branch information
exekias authored and ruflin committed Jul 12, 2018
1 parent e0a366a commit 7b90836
Show file tree
Hide file tree
Showing 14 changed files with 154 additions and 15 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -301,6 +301,7 @@ https://github.com/elastic/beats/compare/v6.2.3...master[Check the HEAD diff]
- Add TLS support to MongoDB module. {pull}7401[7401]
- Added Traefik module with health metricset. {pull}7413[7413]
- Add Elasticsearch ml_job metricsets. {pull}7196[7196]
- Add support for bearer token files to HTTP helper. {pull}7527[7527]

*Packetbeat*

Expand Down
2 changes: 1 addition & 1 deletion metricbeat/autodiscover/appender/kubernetes/token/token.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ type tokenAppender struct {
// NewTokenAppender creates a token appender that can append a bearer token required to authenticate with
// protected endpoints
func NewTokenAppender(cfg *common.Config) (autodiscover.Appender, error) {
cfgwarn.Beta("The token appender is beta")
cfgwarn.Deprecate("7.0.0", "token appender is deprecated in favor of bearer_token_file config parameter")
conf := defaultConfig()

err := cfg.Unpack(&conf)
Expand Down
6 changes: 6 additions & 0 deletions metricbeat/docs/metricbeat-options.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -216,3 +216,9 @@ The username to use for basic authentication.
==== `password`

The password to use for basic authentication.

[float]
==== `bearer_token_file`

If defined, Metricbeat will read the contents of the file once at initialization
and then use the value in an HTTP Authorization header.
4 changes: 3 additions & 1 deletion metricbeat/docs/modules/kubernetes.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,9 @@ metricbeat.modules:
period: 10s
hosts: ["localhost:10255"]
enabled: true
#ssl.certificate_authorities: ["/etc/pki/root/ca.pem"]
#bearer_token_file: /var/run/secrets/kubernetes.io/serviceaccount/token
#ssl.certificate_authorities:
# - /var/run/secrets/kubernetes.io/serviceaccount/service-ca.crt
#ssl.certificate: "/etc/pki/client/cert.pem"
#ssl.key: "/etc/pki/client/cert.key"
Expand Down
5 changes: 5 additions & 0 deletions metricbeat/docs/modules/prometheus.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,11 @@ metricbeat.modules:
hosts: ["localhost:9090"]
#metrics_path: /metrics
#namespace: example
# This can be used for service account based authorization:
# bearer_token_file: /var/run/secrets/kubernetes.io/serviceaccount/token
#ssl.certificate_authorities:
# - /var/run/secrets/kubernetes.io/serviceaccount/service-ca.crt
----

This module supports TLS connection when using `ssl` config field, as described in <<configuration-ssl>>. It also supports the options described in <<module-http-config-options>>.
Expand Down
36 changes: 33 additions & 3 deletions metricbeat/helper/http.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ import (
"net/http"
"time"

"github.com/pkg/errors"

"github.com/elastic/beats/libbeat/common/transport/tlscommon"
"github.com/elastic/beats/libbeat/outputs/transport"
"github.com/elastic/beats/metricbeat/mb"
Expand All @@ -44,9 +46,10 @@ type HTTP struct {
// NewHTTP creates new http helper
func NewHTTP(base mb.BaseMetricSet) (*HTTP, error) {
config := struct {
TLS *tlscommon.Config `config:"ssl"`
Timeout time.Duration `config:"timeout"`
Headers map[string]string `config:"headers"`
TLS *tlscommon.Config `config:"ssl"`
Timeout time.Duration `config:"timeout"`
Headers map[string]string `config:"headers"`
BearerTokenFile string `config:"bearer_token_file"`
}{}
if err := base.Module().UnpackConfig(&config); err != nil {
return nil, err
Expand All @@ -56,6 +59,14 @@ func NewHTTP(base mb.BaseMetricSet) (*HTTP, error) {
config.Headers = map[string]string{}
}

if config.BearerTokenFile != "" {
header, err := getAuthHeaderFromToken(config.BearerTokenFile)
if err != nil {
return nil, err
}
config.Headers["Authorization"] = header
}

tlsConfig, err := tlscommon.LoadTLSConfig(config.TLS)
if err != nil {
return nil, err
Expand Down Expand Up @@ -179,3 +190,22 @@ func (h *HTTP) FetchJSON() (map[string]interface{}, error) {

return data, nil
}

// getAuthHeaderFromToken reads a bearer authorizaiton token from the given file
func getAuthHeaderFromToken(path string) (string, error) {
var token string

b, err := ioutil.ReadFile(path)
if err != nil {
return "", errors.Wrap(err, "reading bearer token file")
}

if len(b) != 0 {
if b[len(b)-1] == '\n' {
b = b[0 : len(b)-1]
}
token = fmt.Sprintf("Bearer %s", string(b))
}

return token, nil
}
71 changes: 71 additions & 0 deletions metricbeat/helper/http_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
// 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 helper

import (
"io/ioutil"
"os"
"testing"

"github.com/stretchr/testify/assert"
)

func TestGetAuthHeaderFromToken(t *testing.T) {
tests := []struct {
Name, Content, Expected string
}{
{
"Test a token is read",
"testtoken",
"Bearer testtoken",
},
{
"Test a token is trimmed",
"testtoken\n",
"Bearer testtoken",
},
}

for _, test := range tests {
t.Run(test.Name, func(t *testing.T) {
content := []byte(test.Content)
tmpfile, err := ioutil.TempFile("", "token")
if err != nil {
t.Fatal(err)
}
defer os.Remove(tmpfile.Name())

if _, err := tmpfile.Write(content); err != nil {
t.Fatal(err)
}
if err := tmpfile.Close(); err != nil {
t.Fatal(err)
}

header, err := getAuthHeaderFromToken(tmpfile.Name())
assert.NoError(t, err)
assert.Equal(t, test.Expected, header)
})
}
}

func TestGetAuthHeaderFromTokenNoFile(t *testing.T) {
header, err := getAuthHeaderFromToken("nonexistingfile")
assert.Equal(t, "", header)
assert.Error(t, err)
}
9 changes: 8 additions & 1 deletion metricbeat/metricbeat.reference.yml
Original file line number Diff line number Diff line change
Expand Up @@ -361,7 +361,9 @@ metricbeat.modules:
period: 10s
hosts: ["localhost:10255"]
enabled: true
#ssl.certificate_authorities: ["/etc/pki/root/ca.pem"]
#bearer_token_file: /var/run/secrets/kubernetes.io/serviceaccount/token
#ssl.certificate_authorities:
# - /var/run/secrets/kubernetes.io/serviceaccount/service-ca.crt
#ssl.certificate: "/etc/pki/client/cert.pem"
#ssl.key: "/etc/pki/client/cert.key"

Expand Down Expand Up @@ -542,6 +544,11 @@ metricbeat.modules:
#metrics_path: /metrics
#namespace: example

# This can be used for service account based authorization:
# bearer_token_file: /var/run/secrets/kubernetes.io/serviceaccount/token
#ssl.certificate_authorities:
# - /var/run/secrets/kubernetes.io/serviceaccount/service-ca.crt

#------------------------------ RabbitMQ Module ------------------------------
- module: rabbitmq
metricsets: ["node", "queue", "connection"]
Expand Down
4 changes: 3 additions & 1 deletion metricbeat/module/kubernetes/_meta/config.reference.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@
period: 10s
hosts: ["localhost:10255"]
enabled: true
#ssl.certificate_authorities: ["/etc/pki/root/ca.pem"]
#bearer_token_file: /var/run/secrets/kubernetes.io/serviceaccount/token
#ssl.certificate_authorities:
# - /var/run/secrets/kubernetes.io/serviceaccount/service-ca.crt
#ssl.certificate: "/etc/pki/client/cert.pem"
#ssl.key: "/etc/pki/client/cert.key"

Expand Down
8 changes: 4 additions & 4 deletions metricbeat/module/kubernetes/_meta/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,10 @@
# - container
# - volume
period: 10s
hosts: ["localhost:10255"]
#ssl.certificate_authorities: ["/etc/pki/root/ca.pem"]
#ssl.certificate: "/etc/pki/client/cert.pem"
#ssl.key: "/etc/pki/client/cert.key"
hosts: ["localhost:10250"]
bearer_token_file: /var/run/secrets/kubernetes.io/serviceaccount/token
ssl.certificate_authorities:
- /var/run/secrets/kubernetes.io/serviceaccount/service-ca.crt
#username: "user"
#password: "secret"

Expand Down
5 changes: 5 additions & 0 deletions metricbeat/module/prometheus/_meta/config.reference.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,8 @@
hosts: ["localhost:9090"]
#metrics_path: /metrics
#namespace: example

# This can be used for service account based authorization:
# bearer_token_file: /var/run/secrets/kubernetes.io/serviceaccount/token
#ssl.certificate_authorities:
# - /var/run/secrets/kubernetes.io/serviceaccount/service-ca.crt
5 changes: 5 additions & 0 deletions metricbeat/module/prometheus/_meta/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,8 @@
#namespace: example
#username: "user"
#password: "secret"

# This can be used for service account based authorization:
# bearer_token_file: /var/run/secrets/kubernetes.io/serviceaccount/token
#ssl.certificate_authorities:
# - /var/run/secrets/kubernetes.io/serviceaccount/service-ca.crt
8 changes: 4 additions & 4 deletions metricbeat/modules.d/kubernetes.yml.disabled
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,10 @@
# - container
# - volume
period: 10s
hosts: ["localhost:10255"]
#ssl.certificate_authorities: ["/etc/pki/root/ca.pem"]
#ssl.certificate: "/etc/pki/client/cert.pem"
#ssl.key: "/etc/pki/client/cert.key"
hosts: ["localhost:10250"]
bearer_token_file: /var/run/secrets/kubernetes.io/serviceaccount/token
ssl.certificate_authorities:
- /var/run/secrets/kubernetes.io/serviceaccount/service-ca.crt
#username: "user"
#password: "secret"

Expand Down
5 changes: 5 additions & 0 deletions metricbeat/modules.d/prometheus.yml.disabled
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,8 @@
#namespace: example
#username: "user"
#password: "secret"

# This can be used for service account based authorization:
# bearer_token_file: /var/run/secrets/kubernetes.io/serviceaccount/token
#ssl.certificate_authorities:
# - /var/run/secrets/kubernetes.io/serviceaccount/service-ca.crt

0 comments on commit 7b90836

Please sign in to comment.