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

Add bearer_token_file paramter to HTTP helper #7527

Merged
merged 7 commits into from
Jul 12, 2018
Merged
Show file tree
Hide file tree
Changes from all 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.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -300,6 +300,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")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you suggest moving to config appender for use cases that require a bearer token then? I wouldnt do a blanket token path on all configs with the hints builder.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, my thought was to use the config appender to set bearer_token_file wherever you were using the token appender before. No need to create new hints for this.
Would that work for you?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

im ok with that. we will do the needful once this is merged.

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
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I realize now this is a new file without license headers, but CI is not complaining. Are we checking that somewhere @andrewkroh?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@exekias Was also looking into this this morning. We check but return 0 after the check. Not sure if that was on purpose (probably not).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the meanwhile, I've pushed the header to this file


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:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it would be useful to say what it does. Something like

It reads the contents of the file once at initialization and then uses the value in an HTTP Authorization header.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Makes sense, I updated docs to include this longer explanation

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should there also be an update to the reference config files? Also applies to the k8s one.

For the paths here for the prometheus config: Should we have k8s as default ones?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think Prometheus is mostly used in k8s scenarios, it should not harm other use cases anyway? I've pushed a commit to include it in

# 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