241 lines
5.8 KiB
Go
241 lines
5.8 KiB
Go
//go:generate ../../../tools/readme_config_includer/generator
|
|
package http
|
|
|
|
import (
|
|
"context"
|
|
_ "embed"
|
|
"encoding/base64"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/blues/jsonata-go"
|
|
|
|
"github.com/influxdata/telegraf"
|
|
"github.com/influxdata/telegraf/config"
|
|
common_http "github.com/influxdata/telegraf/plugins/common/http"
|
|
"github.com/influxdata/telegraf/plugins/secretstores"
|
|
)
|
|
|
|
//go:embed sample.conf
|
|
var sampleConfig string
|
|
|
|
const defaultIdleConnTimeoutMinutes = 5
|
|
|
|
type HTTP struct {
|
|
URL string `toml:"url"`
|
|
Headers map[string]string `toml:"headers"`
|
|
Username config.Secret `toml:"username"`
|
|
Password config.Secret `toml:"password"`
|
|
Token config.Secret `toml:"token"`
|
|
SuccessStatusCodes []int `toml:"success_status_codes"`
|
|
Transformation string `toml:"transformation"`
|
|
Log telegraf.Logger `toml:"-"`
|
|
common_http.HTTPClientConfig
|
|
DecryptionConfig
|
|
|
|
client *http.Client
|
|
transformer *jsonata.Expr
|
|
cache map[string]string
|
|
decrypter Decrypter
|
|
}
|
|
|
|
func (*HTTP) SampleConfig() string {
|
|
return sampleConfig
|
|
}
|
|
|
|
func (h *HTTP) Init() error {
|
|
ctx := context.Background()
|
|
|
|
// Prevent idle connections from hanging around forever on telegraf reload
|
|
if h.HTTPClientConfig.IdleConnTimeout == 0 {
|
|
h.HTTPClientConfig.IdleConnTimeout = config.Duration(defaultIdleConnTimeoutMinutes * time.Minute)
|
|
}
|
|
|
|
client, err := h.HTTPClientConfig.CreateClient(ctx, h.Log)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
h.client = client
|
|
|
|
// Set default as [200]
|
|
if len(h.SuccessStatusCodes) == 0 {
|
|
h.SuccessStatusCodes = []int{200}
|
|
}
|
|
|
|
// Setup the data transformer if any
|
|
if h.Transformation != "" {
|
|
e, err := jsonata.Compile(h.Transformation)
|
|
if err != nil {
|
|
return fmt.Errorf("setting up data transformation failed: %w", err)
|
|
}
|
|
h.transformer = e
|
|
}
|
|
|
|
// Setup the decryption infrastructure
|
|
h.decrypter, err = h.DecryptionConfig.CreateDecrypter()
|
|
if err != nil {
|
|
return fmt.Errorf("creating decryptor failed: %w", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// Get searches for the given key and return the secret
|
|
func (h *HTTP) Get(key string) ([]byte, error) {
|
|
v, found := h.cache[key]
|
|
if !found {
|
|
return nil, errors.New("not found")
|
|
}
|
|
|
|
if h.decrypter != nil {
|
|
// We got binary data delivered in a string, so try to
|
|
// decode it assuming base64-encoding.
|
|
buf, err := base64.StdEncoding.DecodeString(v)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("base64 decoding failed: %w", err)
|
|
}
|
|
return h.decrypter.Decrypt(buf)
|
|
}
|
|
|
|
return []byte(v), nil
|
|
}
|
|
|
|
// Set sets the given secret for the given key
|
|
func (*HTTP) Set(_, _ string) error {
|
|
return errors.New("setting secrets not supported")
|
|
}
|
|
|
|
// List lists all known secret keys
|
|
func (h *HTTP) List() ([]string, error) {
|
|
keys := make([]string, 0, len(h.cache))
|
|
for k := range h.cache {
|
|
keys = append(keys, k)
|
|
}
|
|
return keys, nil
|
|
}
|
|
|
|
// GetResolver returns a function to resolve the given key.
|
|
func (h *HTTP) GetResolver(key string) (telegraf.ResolveFunc, error) {
|
|
// Download and parse the credentials
|
|
if err := h.download(); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
resolver := func() ([]byte, bool, error) {
|
|
s, err := h.Get(key)
|
|
return s, false, err
|
|
}
|
|
return resolver, nil
|
|
}
|
|
|
|
func (h *HTTP) download() error {
|
|
// Get the raw data form the URL
|
|
data, err := h.query()
|
|
if err != nil {
|
|
return fmt.Errorf("reading body failed: %w", err)
|
|
}
|
|
|
|
// Transform the data to the expected form if given
|
|
if h.transformer != nil {
|
|
out, err := h.transformer.EvalBytes(data)
|
|
if err != nil {
|
|
return fmt.Errorf("transforming data failed: %w", err)
|
|
}
|
|
data = out
|
|
}
|
|
|
|
// Extract the data from the resulting data
|
|
if err := json.Unmarshal(data, &h.cache); err != nil {
|
|
var terr *json.UnmarshalTypeError
|
|
if errors.As(err, &terr) {
|
|
return fmt.Errorf("%w; maybe missing or wrong data transformation", err)
|
|
}
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (h *HTTP) query() ([]byte, error) {
|
|
request, err := http.NewRequest(http.MethodGet, h.URL, nil)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("creating request failed: %w", err)
|
|
}
|
|
|
|
for k, v := range h.Headers {
|
|
if strings.EqualFold(k, "host") {
|
|
request.Host = v
|
|
} else {
|
|
request.Header.Add(k, v)
|
|
}
|
|
}
|
|
|
|
if err := h.setRequestAuth(request); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
resp, err := h.client.Do(request)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("executing request failed: %w", err)
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
// Try to wipe the bearer token if any
|
|
request.SetBasicAuth("---", "---")
|
|
request.Header.Set("Authorization", "---")
|
|
|
|
responseHasSuccessCode := false
|
|
for _, statusCode := range h.SuccessStatusCodes {
|
|
if resp.StatusCode == statusCode {
|
|
responseHasSuccessCode = true
|
|
break
|
|
}
|
|
}
|
|
|
|
if !responseHasSuccessCode {
|
|
msg := "received status code %d (%s), expected any value out of %v"
|
|
return nil, fmt.Errorf(msg, resp.StatusCode, http.StatusText(resp.StatusCode), h.SuccessStatusCodes)
|
|
}
|
|
|
|
return io.ReadAll(resp.Body)
|
|
}
|
|
|
|
func (h *HTTP) setRequestAuth(request *http.Request) error {
|
|
if !h.Username.Empty() && !h.Password.Empty() {
|
|
username, err := h.Username.Get()
|
|
if err != nil {
|
|
return fmt.Errorf("getting username failed: %w", err)
|
|
}
|
|
defer username.Destroy()
|
|
password, err := h.Password.Get()
|
|
if err != nil {
|
|
return fmt.Errorf("getting password failed: %w", err)
|
|
}
|
|
defer password.Destroy()
|
|
request.SetBasicAuth(username.String(), password.String())
|
|
}
|
|
|
|
if !h.Token.Empty() {
|
|
token, err := h.Token.Get()
|
|
if err != nil {
|
|
return fmt.Errorf("getting token failed: %w", err)
|
|
}
|
|
defer token.Destroy()
|
|
bearer := "Bearer " + strings.TrimSpace(token.String())
|
|
request.Header.Set("Authorization", bearer)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// Register the secret-store on load.
|
|
func init() {
|
|
secretstores.Add("http", func(string) telegraf.SecretStore {
|
|
return &HTTP{}
|
|
})
|
|
}
|