diff --git a/docs/sources/shared/configuration.md b/docs/sources/shared/configuration.md index 2b025443896a4..9cf3b2a18ed0f 100644 --- a/docs/sources/shared/configuration.md +++ b/docs/sources/shared/configuration.md @@ -967,6 +967,14 @@ Configuration for `analytics`. # URL to which reports are sent # CLI flag: -reporting.usage-stats-url [usage_stats_url: | default = "https://stats.grafana.org/loki-usage-report"] + +# URL to the proxy server +# CLI flag: -reporting.proxy-url +[proxy_url: | default = ""] + +# The TLS configuration. +# The CLI flags prefix for this block configuration is: reporting.tls-config +[tls_config: ] ``` ### attributes_config @@ -2551,6 +2559,7 @@ The `frontend` block configures the Loki query-frontend. [tail_proxy_url: | default = ""] # The TLS configuration. +# The CLI flags prefix for this block configuration is: frontend.tail-tls-config [tail_tls_config: ] ``` @@ -6355,30 +6364,35 @@ chunk_tables_provisioning: ### tls_config -The TLS configuration. +The TLS configuration. The supported CLI flags `` used to reference this configuration block are: + +- `frontend.tail-tls-config` +- `reporting.tls-config` + +  ```yaml # Path to the client certificate, which will be used for authenticating with the # server. Also requires the key path to be configured. -# CLI flag: -frontend.tail-tls-config.tls-cert-path +# CLI flag: -.tls-cert-path [tls_cert_path: | default = ""] # Path to the key for the client certificate. Also requires the client # certificate to be configured. -# CLI flag: -frontend.tail-tls-config.tls-key-path +# CLI flag: -.tls-key-path [tls_key_path: | default = ""] # Path to the CA certificates to validate server certificate against. If not # set, the host's root CA certificates are used. -# CLI flag: -frontend.tail-tls-config.tls-ca-path +# CLI flag: -.tls-ca-path [tls_ca_path: | default = ""] # Override the expected name on the server certificate. -# CLI flag: -frontend.tail-tls-config.tls-server-name +# CLI flag: -.tls-server-name [tls_server_name: | default = ""] # Skip validating server certificate. -# CLI flag: -frontend.tail-tls-config.tls-insecure-skip-verify +# CLI flag: -.tls-insecure-skip-verify [tls_insecure_skip_verify: | default = false] # Override the default cipher suite list (separated by commas). Allowed values: @@ -6411,12 +6425,12 @@ The TLS configuration. # - TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA # - TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256 # - TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256 -# CLI flag: -frontend.tail-tls-config.tls-cipher-suites +# CLI flag: -.tls-cipher-suites [tls_cipher_suites: | default = ""] # Override the default minimum TLS version. Allowed values: VersionTLS10, # VersionTLS11, VersionTLS12, VersionTLS13 -# CLI flag: -frontend.tail-tls-config.tls-min-version +# CLI flag: -.tls-min-version [tls_min_version: | default = ""] ``` diff --git a/pkg/analytics/reporter.go b/pkg/analytics/reporter.go index 47312e6374cd2..e6a279e4022ab 100644 --- a/pkg/analytics/reporter.go +++ b/pkg/analytics/reporter.go @@ -7,6 +7,8 @@ import ( "flag" "io" "math" + "net/http" + "net/url" "os" "time" @@ -14,6 +16,7 @@ import ( "github.com/go-kit/log/level" "github.com/google/uuid" "github.com/grafana/dskit/backoff" + "github.com/grafana/dskit/crypto/tls" "github.com/grafana/dskit/kv" "github.com/grafana/dskit/multierror" "github.com/grafana/dskit/services" @@ -43,15 +46,19 @@ var ( ) type Config struct { - Enabled bool `yaml:"reporting_enabled"` - Leader bool `yaml:"-"` - UsageStatsURL string `yaml:"usage_stats_url"` + Enabled bool `yaml:"reporting_enabled"` + Leader bool `yaml:"-"` + UsageStatsURL string `yaml:"usage_stats_url"` + ProxyURL string `yaml:"proxy_url"` + TLSConfig tls.ClientConfig `yaml:"tls_config"` } // RegisterFlags adds the flags required to config this to the given FlagSet func (cfg *Config) RegisterFlags(f *flag.FlagSet) { f.BoolVar(&cfg.Enabled, "reporting.enabled", true, "Enable anonymous usage reporting.") f.StringVar(&cfg.UsageStatsURL, "reporting.usage-stats-url", usageStatsURL, "URL to which reports are sent") + f.StringVar(&cfg.ProxyURL, "reporting.proxy-url", "", "URL to the proxy server") + cfg.TLSConfig.RegisterFlagsWithPrefix("reporting.tls-config.", f) } type Reporter struct { @@ -61,6 +68,8 @@ type Reporter struct { services.Service + httpClient *http.Client + conf Config kvConfig kv.Config cluster *ClusterSeed @@ -71,12 +80,33 @@ func NewReporter(config Config, kvConfig kv.Config, objectClient client.ObjectCl if !config.Enabled { return nil, nil } + + originalDefaultTransport := http.DefaultTransport.(*http.Transport) + tr := originalDefaultTransport.Clone() + if config.TLSConfig.CertPath != "" || config.TLSConfig.KeyPath != "" { + var err error + tr.TLSClientConfig, err = config.TLSConfig.GetTLSConfig() + if err != nil { + return nil, err + } + } + if config.ProxyURL != "" { + proxyURL, err := url.ParseRequestURI(config.ProxyURL) + if err != nil { + return nil, err + } + tr.Proxy = http.ProxyURL(proxyURL) + } r := &Reporter{ logger: logger, objectClient: objectClient, conf: config, kvConfig: kvConfig, reg: reg, + httpClient: &http.Client{ + Timeout: 5 * time.Second, + Transport: tr, + }, } r.Service = services.NewBasicService(nil, r.running, nil) return r, nil @@ -308,7 +338,7 @@ func (rep *Reporter) reportUsage(ctx context.Context, interval time.Time) error }) var errs multierror.MultiError for backoff.Ongoing() { - if err := sendReport(ctx, rep.cluster, interval, rep.conf.UsageStatsURL); err != nil { + if err := sendReport(ctx, rep.cluster, interval, rep.conf.UsageStatsURL, rep.httpClient); err != nil { level.Info(rep.logger).Log("msg", "failed to send usage report", "retries", backoff.NumRetries(), "err", err) errs.Add(err) backoff.Wait() diff --git a/pkg/analytics/reporter_test.go b/pkg/analytics/reporter_test.go index 889ec8d31e19a..2ab724b592f8b 100644 --- a/pkg/analytics/reporter_test.go +++ b/pkg/analytics/reporter_test.go @@ -170,3 +170,48 @@ func TestStartCPUCollection(t *testing.T) { return cpuUsage.Value() > 0 }, 5*time.Second, 1*time.Second) } + +func Test_ProxyURL(t *testing.T) { + // Create a channel to track received messages + received := make(chan bool, 1) + + // Using this variable to use `http` for this test as `https` is not supported by `httptest`. + target := "http://stats.grafana.org/loki-usage-report" + + // Start local test server + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + require.Equal(t, target, r.URL.String()) + received <- true + w.WriteHeader(http.StatusOK) + })) + defer server.Close() + + proxyStr := server.URL + reporterCfg := Config{ + Leader: true, + Enabled: true, + UsageStatsURL: target, + ProxyURL: proxyStr, + } + reporter, err := NewReporter( + reporterCfg, + kv.Config{ + Store: "inmemory", + }, + nil, + log.NewLogfmtLogger(os.Stdout), + prometheus.NewPedanticRegistry(), + ) + require.NoError(t, err) + reporter.cluster = &ClusterSeed{ + UID: "test", + } + require.NoError(t, reporter.reportUsage(context.Background(), time.Now())) + + // Verify we received the report + select { + case <-received: + case <-time.After(5 * time.Second): + t.Fatal("Timeout waiting for report") + } +} diff --git a/pkg/analytics/stats.go b/pkg/analytics/stats.go index 433a4198c4a1b..664c2b2dd2e05 100644 --- a/pkg/analytics/stats.go +++ b/pkg/analytics/stats.go @@ -23,7 +23,6 @@ import ( ) var ( - httpClient = http.Client{Timeout: 5 * time.Second} usageStatsURL = "https://stats.grafana.org/loki-usage-report" statsPrefix = "github.com/grafana/loki/" targetKey = "target" @@ -73,7 +72,7 @@ type Report struct { } // sendReport sends the report to the stats server -func sendReport(ctx context.Context, seed *ClusterSeed, interval time.Time, URL string) error { +func sendReport(ctx context.Context, seed *ClusterSeed, interval time.Time, URL string, httpClient *http.Client) error { report := buildReport(seed, interval) out, err := jsoniter.MarshalIndent(report, "", " ") if err != nil {