Skip to content

feat: Add TLS config to the analytics client #15227

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

Merged
merged 9 commits into from
Dec 10, 2024
30 changes: 22 additions & 8 deletions docs/sources/shared/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -970,6 +970,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 @@ -2554,6 +2562,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 @@ -6358,30 +6367,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 @@ -6414,12 +6428,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
Loading