Skip to content

Commit

Permalink
feat: Add TLS config to the analytics client (#15227)
Browse files Browse the repository at this point in the history
**What this PR does / why we need it**:
Add a `tls_config` block and a `proxy_url` config to the analytics client. In conjunction they can be useful in scenarios where Loki is running behind a proxy.
  • Loading branch information
DylanGuedes authored Dec 10, 2024
1 parent 00d58e6 commit 2c5eabd
Show file tree
Hide file tree
Showing 4 changed files with 102 additions and 14 deletions.
30 changes: 22 additions & 8 deletions docs/sources/shared/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -967,6 +967,14 @@ Configuration for `analytics`.
# URL to which reports are sent
# CLI flag: -reporting.usage-stats-url
[usage_stats_url: <string> | default = "https://stats.grafana.org/loki-usage-report"]
# URL to the proxy server
# CLI flag: -reporting.proxy-url
[proxy_url: <string> | default = ""]
# The TLS configuration.
# The CLI flags prefix for this block configuration is: reporting.tls-config
[tls_config: <tls_config>]
```

### attributes_config
Expand Down Expand Up @@ -2551,6 +2559,7 @@ The `frontend` block configures the Loki query-frontend.
[tail_proxy_url: <string> | default = ""]
# The TLS configuration.
# The CLI flags prefix for this block configuration is: frontend.tail-tls-config
[tail_tls_config: <tls_config>]
```

Expand Down Expand Up @@ -6355,30 +6364,35 @@ chunk_tables_provisioning:

### tls_config

The TLS configuration.
The TLS configuration. The supported CLI flags `<prefix>` used to reference this configuration block are:

- `frontend.tail-tls-config`
- `reporting.tls-config`

&nbsp;

```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: -<prefix>.tls-cert-path
[tls_cert_path: <string> | 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: -<prefix>.tls-key-path
[tls_key_path: <string> | 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: -<prefix>.tls-ca-path
[tls_ca_path: <string> | default = ""]
# Override the expected name on the server certificate.
# CLI flag: -frontend.tail-tls-config.tls-server-name
# CLI flag: -<prefix>.tls-server-name
[tls_server_name: <string> | default = ""]
# Skip validating server certificate.
# CLI flag: -frontend.tail-tls-config.tls-insecure-skip-verify
# CLI flag: -<prefix>.tls-insecure-skip-verify
[tls_insecure_skip_verify: <boolean> | default = false]
# Override the default cipher suite list (separated by commas). Allowed values:
Expand Down Expand Up @@ -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: -<prefix>.tls-cipher-suites
[tls_cipher_suites: <string> | default = ""]
# Override the default minimum TLS version. Allowed values: VersionTLS10,
# VersionTLS11, VersionTLS12, VersionTLS13
# CLI flag: -frontend.tail-tls-config.tls-min-version
# CLI flag: -<prefix>.tls-min-version
[tls_min_version: <string> | default = ""]
```

Expand Down
38 changes: 34 additions & 4 deletions pkg/analytics/reporter.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,16 @@ import (
"flag"
"io"
"math"
"net/http"
"net/url"
"os"
"time"

"github.com/go-kit/log"
"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"
Expand Down Expand Up @@ -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 {
Expand All @@ -61,6 +68,8 @@ type Reporter struct {

services.Service

httpClient *http.Client

conf Config
kvConfig kv.Config
cluster *ClusterSeed
Expand All @@ -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
Expand Down Expand Up @@ -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()
Expand Down
45 changes: 45 additions & 0 deletions pkg/analytics/reporter_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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")
}
}
3 changes: 1 addition & 2 deletions pkg/analytics/stats.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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 {
Expand Down

0 comments on commit 2c5eabd

Please sign in to comment.