Skip to content

Commit 2c5eabd

Browse files
authored
feat: Add TLS config to the analytics client (#15227)
**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.
1 parent 00d58e6 commit 2c5eabd

File tree

4 files changed

+102
-14
lines changed

4 files changed

+102
-14
lines changed

‎docs/sources/shared/configuration.md

+22-8
Original file line numberDiff line numberDiff line change
@@ -967,6 +967,14 @@ Configuration for `analytics`.
967967
# URL to which reports are sent
968968
# CLI flag: -reporting.usage-stats-url
969969
[usage_stats_url: <string> | default = "https://stats.grafana.org/loki-usage-report"]
970+
971+
# URL to the proxy server
972+
# CLI flag: -reporting.proxy-url
973+
[proxy_url: <string> | default = ""]
974+
975+
# The TLS configuration.
976+
# The CLI flags prefix for this block configuration is: reporting.tls-config
977+
[tls_config: <tls_config>]
970978
```
971979

972980
### attributes_config
@@ -2551,6 +2559,7 @@ The `frontend` block configures the Loki query-frontend.
25512559
[tail_proxy_url: <string> | default = ""]
25522560
25532561
# The TLS configuration.
2562+
# The CLI flags prefix for this block configuration is: frontend.tail-tls-config
25542563
[tail_tls_config: <tls_config>]
25552564
```
25562565

@@ -6355,30 +6364,35 @@ chunk_tables_provisioning:
63556364

63566365
### tls_config
63576366

6358-
The TLS configuration.
6367+
The TLS configuration. The supported CLI flags `<prefix>` used to reference this configuration block are:
6368+
6369+
- `frontend.tail-tls-config`
6370+
- `reporting.tls-config`
6371+
6372+
&nbsp;
63596373

63606374
```yaml
63616375
# Path to the client certificate, which will be used for authenticating with the
63626376
# server. Also requires the key path to be configured.
6363-
# CLI flag: -frontend.tail-tls-config.tls-cert-path
6377+
# CLI flag: -<prefix>.tls-cert-path
63646378
[tls_cert_path: <string> | default = ""]
63656379
63666380
# Path to the key for the client certificate. Also requires the client
63676381
# certificate to be configured.
6368-
# CLI flag: -frontend.tail-tls-config.tls-key-path
6382+
# CLI flag: -<prefix>.tls-key-path
63696383
[tls_key_path: <string> | default = ""]
63706384
63716385
# Path to the CA certificates to validate server certificate against. If not
63726386
# set, the host's root CA certificates are used.
6373-
# CLI flag: -frontend.tail-tls-config.tls-ca-path
6387+
# CLI flag: -<prefix>.tls-ca-path
63746388
[tls_ca_path: <string> | default = ""]
63756389
63766390
# Override the expected name on the server certificate.
6377-
# CLI flag: -frontend.tail-tls-config.tls-server-name
6391+
# CLI flag: -<prefix>.tls-server-name
63786392
[tls_server_name: <string> | default = ""]
63796393
63806394
# Skip validating server certificate.
6381-
# CLI flag: -frontend.tail-tls-config.tls-insecure-skip-verify
6395+
# CLI flag: -<prefix>.tls-insecure-skip-verify
63826396
[tls_insecure_skip_verify: <boolean> | default = false]
63836397
63846398
# Override the default cipher suite list (separated by commas). Allowed values:
@@ -6411,12 +6425,12 @@ The TLS configuration.
64116425
# - TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA
64126426
# - TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256
64136427
# - TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256
6414-
# CLI flag: -frontend.tail-tls-config.tls-cipher-suites
6428+
# CLI flag: -<prefix>.tls-cipher-suites
64156429
[tls_cipher_suites: <string> | default = ""]
64166430
64176431
# Override the default minimum TLS version. Allowed values: VersionTLS10,
64186432
# VersionTLS11, VersionTLS12, VersionTLS13
6419-
# CLI flag: -frontend.tail-tls-config.tls-min-version
6433+
# CLI flag: -<prefix>.tls-min-version
64206434
[tls_min_version: <string> | default = ""]
64216435
```
64226436

‎pkg/analytics/reporter.go

+34-4
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,16 @@ import (
77
"flag"
88
"io"
99
"math"
10+
"net/http"
11+
"net/url"
1012
"os"
1113
"time"
1214

1315
"github.com/go-kit/log"
1416
"github.com/go-kit/log/level"
1517
"github.com/google/uuid"
1618
"github.com/grafana/dskit/backoff"
19+
"github.com/grafana/dskit/crypto/tls"
1720
"github.com/grafana/dskit/kv"
1821
"github.com/grafana/dskit/multierror"
1922
"github.com/grafana/dskit/services"
@@ -43,15 +46,19 @@ var (
4346
)
4447

4548
type Config struct {
46-
Enabled bool `yaml:"reporting_enabled"`
47-
Leader bool `yaml:"-"`
48-
UsageStatsURL string `yaml:"usage_stats_url"`
49+
Enabled bool `yaml:"reporting_enabled"`
50+
Leader bool `yaml:"-"`
51+
UsageStatsURL string `yaml:"usage_stats_url"`
52+
ProxyURL string `yaml:"proxy_url"`
53+
TLSConfig tls.ClientConfig `yaml:"tls_config"`
4954
}
5055

5156
// RegisterFlags adds the flags required to config this to the given FlagSet
5257
func (cfg *Config) RegisterFlags(f *flag.FlagSet) {
5358
f.BoolVar(&cfg.Enabled, "reporting.enabled", true, "Enable anonymous usage reporting.")
5459
f.StringVar(&cfg.UsageStatsURL, "reporting.usage-stats-url", usageStatsURL, "URL to which reports are sent")
60+
f.StringVar(&cfg.ProxyURL, "reporting.proxy-url", "", "URL to the proxy server")
61+
cfg.TLSConfig.RegisterFlagsWithPrefix("reporting.tls-config.", f)
5562
}
5663

5764
type Reporter struct {
@@ -61,6 +68,8 @@ type Reporter struct {
6168

6269
services.Service
6370

71+
httpClient *http.Client
72+
6473
conf Config
6574
kvConfig kv.Config
6675
cluster *ClusterSeed
@@ -71,12 +80,33 @@ func NewReporter(config Config, kvConfig kv.Config, objectClient client.ObjectCl
7180
if !config.Enabled {
7281
return nil, nil
7382
}
83+
84+
originalDefaultTransport := http.DefaultTransport.(*http.Transport)
85+
tr := originalDefaultTransport.Clone()
86+
if config.TLSConfig.CertPath != "" || config.TLSConfig.KeyPath != "" {
87+
var err error
88+
tr.TLSClientConfig, err = config.TLSConfig.GetTLSConfig()
89+
if err != nil {
90+
return nil, err
91+
}
92+
}
93+
if config.ProxyURL != "" {
94+
proxyURL, err := url.ParseRequestURI(config.ProxyURL)
95+
if err != nil {
96+
return nil, err
97+
}
98+
tr.Proxy = http.ProxyURL(proxyURL)
99+
}
74100
r := &Reporter{
75101
logger: logger,
76102
objectClient: objectClient,
77103
conf: config,
78104
kvConfig: kvConfig,
79105
reg: reg,
106+
httpClient: &http.Client{
107+
Timeout: 5 * time.Second,
108+
Transport: tr,
109+
},
80110
}
81111
r.Service = services.NewBasicService(nil, r.running, nil)
82112
return r, nil
@@ -308,7 +338,7 @@ func (rep *Reporter) reportUsage(ctx context.Context, interval time.Time) error
308338
})
309339
var errs multierror.MultiError
310340
for backoff.Ongoing() {
311-
if err := sendReport(ctx, rep.cluster, interval, rep.conf.UsageStatsURL); err != nil {
341+
if err := sendReport(ctx, rep.cluster, interval, rep.conf.UsageStatsURL, rep.httpClient); err != nil {
312342
level.Info(rep.logger).Log("msg", "failed to send usage report", "retries", backoff.NumRetries(), "err", err)
313343
errs.Add(err)
314344
backoff.Wait()

‎pkg/analytics/reporter_test.go

+45
Original file line numberDiff line numberDiff line change
@@ -170,3 +170,48 @@ func TestStartCPUCollection(t *testing.T) {
170170
return cpuUsage.Value() > 0
171171
}, 5*time.Second, 1*time.Second)
172172
}
173+
174+
func Test_ProxyURL(t *testing.T) {
175+
// Create a channel to track received messages
176+
received := make(chan bool, 1)
177+
178+
// Using this variable to use `http` for this test as `https` is not supported by `httptest`.
179+
target := "http://stats.grafana.org/loki-usage-report"
180+
181+
// Start local test server
182+
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
183+
require.Equal(t, target, r.URL.String())
184+
received <- true
185+
w.WriteHeader(http.StatusOK)
186+
}))
187+
defer server.Close()
188+
189+
proxyStr := server.URL
190+
reporterCfg := Config{
191+
Leader: true,
192+
Enabled: true,
193+
UsageStatsURL: target,
194+
ProxyURL: proxyStr,
195+
}
196+
reporter, err := NewReporter(
197+
reporterCfg,
198+
kv.Config{
199+
Store: "inmemory",
200+
},
201+
nil,
202+
log.NewLogfmtLogger(os.Stdout),
203+
prometheus.NewPedanticRegistry(),
204+
)
205+
require.NoError(t, err)
206+
reporter.cluster = &ClusterSeed{
207+
UID: "test",
208+
}
209+
require.NoError(t, reporter.reportUsage(context.Background(), time.Now()))
210+
211+
// Verify we received the report
212+
select {
213+
case <-received:
214+
case <-time.After(5 * time.Second):
215+
t.Fatal("Timeout waiting for report")
216+
}
217+
}

‎pkg/analytics/stats.go

+1-2
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@ import (
2323
)
2424

2525
var (
26-
httpClient = http.Client{Timeout: 5 * time.Second}
2726
usageStatsURL = "https://stats.grafana.org/loki-usage-report"
2827
statsPrefix = "github.com/grafana/loki/"
2928
targetKey = "target"
@@ -73,7 +72,7 @@ type Report struct {
7372
}
7473

7574
// sendReport sends the report to the stats server
76-
func sendReport(ctx context.Context, seed *ClusterSeed, interval time.Time, URL string) error {
75+
func sendReport(ctx context.Context, seed *ClusterSeed, interval time.Time, URL string, httpClient *http.Client) error {
7776
report := buildReport(seed, interval)
7877
out, err := jsoniter.MarshalIndent(report, "", " ")
7978
if err != nil {

0 commit comments

Comments
 (0)