remote_write: allow passing along custom HTTP headers (#8416)

* remote_write: allow passing along custom HTTP headers

Signed-off-by: Nandor Kracser <bonifaido@gmail.com>

* add warning

Signed-off-by: Nandor Kracser <bonifaido@gmail.com>

* remote_write: add header valadtion

Signed-off-by: Nandor Kracser <bonifaido@gmail.com>

* extend tests for bad remote write headers

Signed-off-by: Nandor Kracser <bonifaido@gmail.com>

* remote_write: add note about the authorization header

Signed-off-by: Nandor Kracser <bonifaido@gmail.com>
This commit is contained in:
Nándor István Krácser 2021-02-04 22:18:13 +01:00 committed by GitHub
parent 7db09551b0
commit 509000269a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 56 additions and 1 deletions

View file

@ -34,6 +34,21 @@ import (
var ( var (
patRulePath = regexp.MustCompile(`^[^*]*(\*[^/]*)?$`) patRulePath = regexp.MustCompile(`^[^*]*(\*[^/]*)?$`)
unchangeableHeaders = map[string]struct{}{
// NOTE: authorization is checked specially,
// see RemoteWriteConfig.UnmarshalYAML.
// "authorization": {},
"host": {},
"content-encoding": {},
"content-type": {},
"x-prometheus-remote-write-version": {},
"user-agent": {},
"connection": {},
"keep-alive": {},
"proxy-authenticate": {},
"proxy-authorization": {},
"www-authenticate": {},
}
) )
// Load parses the YAML input s into a Config. // Load parses the YAML input s into a Config.
@ -570,6 +585,7 @@ func CheckTargetAddress(address model.LabelValue) error {
type RemoteWriteConfig struct { type RemoteWriteConfig struct {
URL *config.URL `yaml:"url"` URL *config.URL `yaml:"url"`
RemoteTimeout model.Duration `yaml:"remote_timeout,omitempty"` RemoteTimeout model.Duration `yaml:"remote_timeout,omitempty"`
Headers map[string]string `yaml:"headers,omitempty"`
WriteRelabelConfigs []*relabel.Config `yaml:"write_relabel_configs,omitempty"` WriteRelabelConfigs []*relabel.Config `yaml:"write_relabel_configs,omitempty"`
Name string `yaml:"name,omitempty"` Name string `yaml:"name,omitempty"`
@ -600,6 +616,14 @@ func (c *RemoteWriteConfig) UnmarshalYAML(unmarshal func(interface{}) error) err
return errors.New("empty or null relabeling rule in remote write config") return errors.New("empty or null relabeling rule in remote write config")
} }
} }
for header := range c.Headers {
if strings.ToLower(header) == "authorization" {
return errors.New("authorization header must be changed via the basic_auth, bearer_token, or bearer_token_file parameter")
}
if _, ok := unchangeableHeaders[strings.ToLower(header)]; ok {
return errors.Errorf("%s is an unchangeable header", header)
}
}
// The UnmarshalYAML method of HTTPClientConfig is not being called because it's not a pointer. // The UnmarshalYAML method of HTTPClientConfig is not being called because it's not a pointer.
// We cannot make it a pointer as the parser panics for inlined pointer structs. // We cannot make it a pointer as the parser panics for inlined pointer structs.

View file

@ -102,6 +102,7 @@ var expectedConf = &Config{
KeyFile: filepath.FromSlash("testdata/valid_key_file"), KeyFile: filepath.FromSlash("testdata/valid_key_file"),
}, },
}, },
Headers: map[string]string{"name": "value"},
}, },
}, },
@ -937,6 +938,12 @@ var expectedErrors = []struct {
}, { }, {
filename: "remote_read_url_missing.bad.yml", filename: "remote_read_url_missing.bad.yml",
errMsg: `url for remote_read is empty`, errMsg: `url for remote_read is empty`,
}, {
filename: "remote_write_header.bad.yml",
errMsg: `x-prometheus-remote-write-version is an unchangeable header`,
}, {
filename: "remote_write_authorization_header.bad.yml",
errMsg: `authorization header must be changed via the basic_auth, bearer_token, or bearer_token_file parameter`,
}, { }, {
filename: "remote_write_url_missing.bad.yml", filename: "remote_write_url_missing.bad.yml",
errMsg: `url for remote_write is empty`, errMsg: `url for remote_write is empty`,

View file

@ -24,6 +24,8 @@ remote_write:
tls_config: tls_config:
cert_file: valid_cert_file cert_file: valid_cert_file
key_file: valid_key_file key_file: valid_key_file
headers:
name: value
remote_read: remote_read:
- url: http://remote1/read - url: http://remote1/read

View file

@ -0,0 +1,5 @@
remote_write:
- url: localhost:9090
name: queue1
headers:
"authorization": "Basic YWxhZGRpbjpvcGVuc2VzYW1l"

View file

@ -0,0 +1,5 @@
remote_write:
- url: localhost:9090
name: queue1
headers:
"x-prometheus-remote-write-version": "somehack"

View file

@ -1719,6 +1719,11 @@ url: <string>
# Timeout for requests to the remote write endpoint. # Timeout for requests to the remote write endpoint.
[ remote_timeout: <duration> | default = 30s ] [ remote_timeout: <duration> | default = 30s ]
# Custom HTTP headers to be sent along with each remote write request.
# Be aware that headers that are set by Prometheus itself can't be overwritten.
headers:
[ <string>: <string> ... ]
# List of remote write relabel configurations. # List of remote write relabel configurations.
write_relabel_configs: write_relabel_configs:
[ - <relabel_config> ... ] [ - <relabel_config> ... ]

View file

@ -83,6 +83,7 @@ type Client struct {
url *config_util.URL url *config_util.URL
Client *http.Client Client *http.Client
timeout time.Duration timeout time.Duration
headers map[string]string
readQueries prometheus.Gauge readQueries prometheus.Gauge
readQueriesTotal *prometheus.CounterVec readQueriesTotal *prometheus.CounterVec
@ -94,6 +95,7 @@ type ClientConfig struct {
URL *config_util.URL URL *config_util.URL
Timeout model.Duration Timeout model.Duration
HTTPClientConfig config_util.HTTPClientConfig HTTPClientConfig config_util.HTTPClientConfig
Headers map[string]string
} }
// ReadClient uses the SAMPLES method of remote read to read series samples from remote server. // ReadClient uses the SAMPLES method of remote read to read series samples from remote server.
@ -142,6 +144,7 @@ func NewWriteClient(name string, conf *ClientConfig) (WriteClient, error) {
url: conf.URL, url: conf.URL,
Client: httpClient, Client: httpClient,
timeout: time.Duration(conf.Timeout), timeout: time.Duration(conf.Timeout),
headers: conf.Headers,
}, nil }, nil
} }
@ -158,6 +161,9 @@ func (c *Client) Store(ctx context.Context, req []byte) error {
// recoverable. // recoverable.
return err return err
} }
for k, v := range c.headers {
httpReq.Header.Set(k, v)
}
httpReq.Header.Add("Content-Encoding", "snappy") httpReq.Header.Add("Content-Encoding", "snappy")
httpReq.Header.Set("Content-Type", "application/x-protobuf") httpReq.Header.Set("Content-Type", "application/x-protobuf")
httpReq.Header.Set("User-Agent", UserAgent) httpReq.Header.Set("User-Agent", UserAgent)

View file

@ -134,6 +134,7 @@ func (rws *WriteStorage) ApplyConfig(conf *config.Config) error {
URL: rwConf.URL, URL: rwConf.URL,
Timeout: rwConf.RemoteTimeout, Timeout: rwConf.RemoteTimeout,
HTTPClientConfig: rwConf.HTTPClientConfig, HTTPClientConfig: rwConf.HTTPClientConfig,
Headers: rwConf.Headers,
}) })
if err != nil { if err != nil {
return err return err