Skip to content

Commit 3f2b481

Browse files
authored
Merge pull request #772 from slok/slok/k8s-1-35
Add basic auth to prometheus client
2 parents 1876e29 + 1ddfa25 commit 3f2b481

File tree

1 file changed

+107
-0
lines changed

1 file changed

+107
-0
lines changed

cmd/sloth/commands/server.go

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,12 @@ package commands
22

33
import (
44
"context"
5+
"crypto/tls"
6+
"crypto/x509"
57
"fmt"
68
"net/http"
79
"net/http/pprof"
10+
"os"
811
"os/signal"
912
"syscall"
1013
"time"
@@ -43,6 +46,17 @@ type serverCommand struct {
4346
fake bool
4447
promAddress string
4548
cacheInstantRefreshInterval time.Duration
49+
auth struct {
50+
basicUser string
51+
basicPassword string
52+
}
53+
tls struct {
54+
insecureSkipVerify bool
55+
caFile string
56+
certFile string
57+
keyFile string
58+
}
59+
headers map[string]string
4660
}
4761
}
4862

@@ -59,6 +73,13 @@ func NewServerCommand(app *kingpin.Application) Command {
5973
cmd.Flag("fake-prometheus", "Enable fake Prometheus server.").BoolVar(&c.prometheus.fake)
6074
cmd.Flag("prometheus-address", "Prometheus server address.").Default("http://localhost:9090").StringVar(&c.prometheus.promAddress)
6175
cmd.Flag("prometheus-cache-refresh-interval", "The interval for Prometheus cache instant data refresh refresh.").Default("1m").DurationVar(&c.prometheus.cacheInstantRefreshInterval)
76+
cmd.Flag("prometheus-auth-basic-user", "Basic auth user for Prometheus.").StringVar(&c.prometheus.auth.basicUser)
77+
cmd.Flag("prometheus-auth-basic-password", "Basic auth password for Prometheus.").StringVar(&c.prometheus.auth.basicPassword)
78+
cmd.Flag("prometheus-tls-insecure-skip-verify", "Skip TLS certificate verification for Prometheus client.").BoolVar(&c.prometheus.tls.insecureSkipVerify)
79+
cmd.Flag("prometheus-tls-ca-file", "CA certificate file for Prometheus client TLS.").StringVar(&c.prometheus.tls.caFile)
80+
cmd.Flag("prometheus-tls-cert-file", "Client certificate file for Prometheus client mTLS.").StringVar(&c.prometheus.tls.certFile)
81+
cmd.Flag("prometheus-tls-key-file", "Client key file for Prometheus client mTLS.").StringVar(&c.prometheus.tls.keyFile)
82+
cmd.Flag("prometheus-header", "Custom header for Prometheus client (format: 'key=value'). Can be repeated for multiple headers.").Short('h').StringMapVar(&c.prometheus.headers)
6283

6384
return c
6485
}
@@ -158,9 +179,40 @@ func (c serverCommand) Run(ctx context.Context, config RootConfig) error {
158179
logger.Warningf("Using fake Prometheus storage backend")
159180
repo = storagefake.NewFakeRepository()
160181
case c.prometheus.promAddress != "":
182+
// Create HTTP transport with optional TLS configuration.
183+
transport := http.DefaultTransport.(*http.Transport).Clone()
184+
185+
// Configure TLS if any TLS options are set.
186+
if c.prometheus.tls.insecureSkipVerify || c.prometheus.tls.caFile != "" || c.prometheus.tls.certFile != "" {
187+
tlsConfig, err := c.buildPrometheusTLSConfig()
188+
if err != nil {
189+
return fmt.Errorf("could not build TLS config: %w", err)
190+
}
191+
transport.TLSClientConfig = tlsConfig
192+
}
193+
194+
var roundTripper http.RoundTripper = transport
195+
196+
// Add auth and custom headers if configured.
197+
if c.prometheus.auth.basicUser != "" || c.prometheus.auth.basicPassword != "" || len(c.prometheus.headers) > 0 {
198+
roundTripper = &authHeadersRoundTripper{
199+
basicAuthUser: c.prometheus.auth.basicUser,
200+
basicAuthPass: c.prometheus.auth.basicPassword,
201+
headers: c.prometheus.headers,
202+
next: roundTripper,
203+
}
204+
}
205+
206+
httpClient := &http.Client{
207+
Timeout: 1 * time.Minute, // At least we end at some point.
208+
Transport: roundTripper,
209+
}
210+
161211
logger.Infof("Using Prometheus storage backend at %s", c.prometheus.promAddress)
212+
162213
client, err := promapi.NewClient(promapi.Config{
163214
Address: c.prometheus.promAddress,
215+
Client: httpClient,
164216
})
165217
if err != nil {
166218
return fmt.Errorf("could not create prometheus api client: %w", err)
@@ -248,6 +300,39 @@ func (c serverCommand) Run(ctx context.Context, config RootConfig) error {
248300
return nil
249301
}
250302

303+
func (c *serverCommand) buildPrometheusTLSConfig() (*tls.Config, error) {
304+
tlsConfig := &tls.Config{
305+
InsecureSkipVerify: c.prometheus.tls.insecureSkipVerify,
306+
}
307+
308+
// Load CA certificate if provided.
309+
if c.prometheus.tls.caFile != "" {
310+
caCert, err := os.ReadFile(c.prometheus.tls.caFile)
311+
if err != nil {
312+
return nil, fmt.Errorf("could not read CA file: %w", err)
313+
}
314+
315+
caCertPool := x509.NewCertPool()
316+
if !caCertPool.AppendCertsFromPEM(caCert) {
317+
return nil, fmt.Errorf("failed to parse CA certificate")
318+
}
319+
tlsConfig.RootCAs = caCertPool
320+
}
321+
322+
// Load client certificate and key for mTLS if provided.
323+
if c.prometheus.tls.certFile != "" && c.prometheus.tls.keyFile != "" {
324+
cert, err := tls.LoadX509KeyPair(c.prometheus.tls.certFile, c.prometheus.tls.keyFile)
325+
if err != nil {
326+
return nil, fmt.Errorf("could not load client certificate: %w", err)
327+
}
328+
tlsConfig.Certificates = []tls.Certificate{cert}
329+
} else if c.prometheus.tls.certFile != "" || c.prometheus.tls.keyFile != "" {
330+
return nil, fmt.Errorf("both cert-file and key-file must be provided for mTLS")
331+
}
332+
333+
return tlsConfig, nil
334+
}
335+
251336
type unifiedRepository interface {
252337
storage.SLOGetter
253338
storage.ServiceGetter
@@ -262,3 +347,25 @@ func newMeasuredUnifiedRepository(orig unifiedRepository, metricsRecorder httpba
262347
ServiceGetter: storagewrappers.NewMeasuredServiceGetter(orig, metricsRecorder),
263348
}
264349
}
350+
351+
// authHeadersRoundTripper adds basic auth and custom headers to HTTP requests.
352+
type authHeadersRoundTripper struct {
353+
basicAuthUser string
354+
basicAuthPass string
355+
headers map[string]string
356+
next http.RoundTripper
357+
}
358+
359+
func (rt *authHeadersRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
360+
// Add basic auth if configured.
361+
if rt.basicAuthUser != "" || rt.basicAuthPass != "" {
362+
req.SetBasicAuth(rt.basicAuthUser, rt.basicAuthPass)
363+
}
364+
365+
// Add custom headers.
366+
for key, value := range rt.headers {
367+
req.Header.Set(key, value)
368+
}
369+
370+
return rt.next.RoundTrip(req)
371+
}

0 commit comments

Comments
 (0)