Skip to content
This repository was archived by the owner on Mar 18, 2025. It is now read-only.

Commit dc4b04f

Browse files
codebienraoel
andauthored
TLS certificates (#143)
It adds options and the eventual usage for client certificates. It is useful for supporting the mTLS use case. Check the following docs for the details: - https://prometheus.io/docs/guides/tls-encryption - https://www.cloudflare.com/learning/access-management/what-is-mutual-tls --------- Co-authored-by: Raoel Oomen <Raoel.Oomen@topicus.nl>
1 parent 026b44a commit dc4b04f

File tree

2 files changed

+87
-2
lines changed

2 files changed

+87
-2
lines changed

pkg/remotewrite/config.go

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,16 @@ type Config struct {
4141
// Password is the Password for the Basic Auth.
4242
Password null.String `json:"password"`
4343

44+
// ClientCertificate is the public key of the SSL certificate.
45+
// It is expected the path of the certificate on the file system.
46+
// If it is required a dedicated Certifacate Authority then it should be added
47+
// to the conventional folders defined by the operating system's registry.
48+
ClientCertificate null.String `json:"clientCertificate"`
49+
50+
// ClientCertificateKey is the private key of the SSL certificate.
51+
// It is expected the path of the certificate on the file system.
52+
ClientCertificateKey null.String `json:"clientCertificateKey"`
53+
4454
// BearerToken if set is the token used for the `Authorization` header.
4555
BearerToken null.String `json:"bearerToken"`
4656

@@ -92,6 +102,14 @@ func (conf Config) RemoteConfig() (*remote.HTTPConfig, error) {
92102
InsecureSkipVerify: conf.InsecureSkipTLSVerify.Bool, //nolint:gosec
93103
}
94104

105+
if conf.ClientCertificate.Valid && conf.ClientCertificateKey.Valid {
106+
cert, err := tls.LoadX509KeyPair(conf.ClientCertificate.String, conf.ClientCertificateKey.String)
107+
if err != nil {
108+
return nil, fmt.Errorf("failed to load the TLS certificate: %w", err)
109+
}
110+
hc.TLSConfig.Certificates = []tls.Certificate{cert}
111+
}
112+
95113
if len(conf.Headers) > 0 {
96114
hc.Headers = make(http.Header)
97115
for k, v := range conf.Headers {
@@ -150,6 +168,14 @@ func (conf Config) Apply(applied Config) Config {
150168
copy(conf.TrendStats, applied.TrendStats)
151169
}
152170

171+
if applied.ClientCertificate.Valid {
172+
conf.ClientCertificate = applied.ClientCertificate
173+
}
174+
175+
if applied.ClientCertificateKey.Valid {
176+
conf.ClientCertificateKey = applied.ClientCertificateKey
177+
}
178+
153179
return conf
154180
}
155181

@@ -242,6 +268,14 @@ func parseEnvs(env map[string]string) (Config, error) {
242268
c.Password = null.StringFrom(password)
243269
}
244270

271+
if clientCertificate, certDefined := env["K6_PROMETHEUS_RW_CLIENT_CERTIFICATE"]; certDefined {
272+
c.ClientCertificate = null.StringFrom(clientCertificate)
273+
}
274+
275+
if clientCertificateKey, certDefined := env["K6_PROMETHEUS_RW_CLIENT_CERTIFICATE_KEY"]; certDefined {
276+
c.ClientCertificateKey = null.StringFrom(clientCertificateKey)
277+
}
278+
245279
envHeaders := getEnvMap(env, "K6_PROMETHEUS_RW_HEADERS_")
246280
for k, v := range envHeaders {
247281
c.Headers[k] = v
@@ -324,6 +358,11 @@ func parseArg(text string) (Config, error) {
324358
//c.TrendStats = strings.Split(v, ",")
325359
//}
326360

361+
case "clientCertificate":
362+
c.ClientCertificate = null.StringFrom(v)
363+
case "clientCertificateKey":
364+
c.ClientCertificateKey = null.StringFrom(v)
365+
327366
default:
328367
if !strings.HasPrefix(key, "headers.") {
329368
return c, fmt.Errorf("%q is an unknown option's key", r[0])

pkg/remotewrite/config_test.go

Lines changed: 48 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,19 @@ func TestConfigRemoteConfig(t *testing.T) {
8686
assert.Equal(t, exprcc, rcc)
8787
}
8888

89+
func TestConfigRemoteConfigClientCertificateError(t *testing.T) {
90+
t.Parallel()
91+
92+
config := Config{
93+
ClientCertificate: null.StringFrom("bad-cert-value"),
94+
ClientCertificateKey: null.StringFrom("bad-cert-key"),
95+
}
96+
97+
rcc, err := config.RemoteConfig()
98+
assert.ErrorContains(t, err, "TLS certificate")
99+
assert.Nil(t, rcc)
100+
}
101+
89102
func TestGetConsolidatedConfig(t *testing.T) {
90103
t.Parallel()
91104

@@ -369,6 +382,41 @@ func TestOptionBasicAuth(t *testing.T) {
369382
}
370383
}
371384

385+
func TestOptionClientCertificate(t *testing.T) {
386+
t.Parallel()
387+
388+
cases := map[string]struct {
389+
arg string
390+
env map[string]string
391+
jsonRaw json.RawMessage
392+
}{
393+
"JSON": {jsonRaw: json.RawMessage(`{"clientCertificate":"client.crt","clientCertificateKey":"client.key"}`)},
394+
"Env": {env: map[string]string{"K6_PROMETHEUS_RW_CLIENT_CERTIFICATE": "client.crt", "K6_PROMETHEUS_RW_CLIENT_CERTIFICATE_KEY": "client.key"}},
395+
}
396+
397+
expconfig := Config{
398+
ServerURL: null.StringFrom("http://localhost:9090/api/v1/write"),
399+
InsecureSkipTLSVerify: null.BoolFrom(false),
400+
PushInterval: types.NullDurationFrom(5 * time.Second),
401+
Headers: make(map[string]string),
402+
TrendStats: []string{"p(99)"},
403+
ClientCertificate: null.StringFrom("client.crt"),
404+
ClientCertificateKey: null.StringFrom("client.key"),
405+
StaleMarkers: null.BoolFrom(false),
406+
}
407+
408+
for name, tc := range cases {
409+
tc := tc
410+
t.Run(name, func(t *testing.T) {
411+
t.Parallel()
412+
c, err := GetConsolidatedConfig(
413+
tc.jsonRaw, tc.env, tc.arg)
414+
require.NoError(t, err)
415+
assert.Equal(t, expconfig, c)
416+
})
417+
}
418+
}
419+
372420
func TestOptionTrendAsNativeHistogram(t *testing.T) {
373421
t.Parallel()
374422

@@ -417,8 +465,6 @@ func TestOptionPushInterval(t *testing.T) {
417465
}{
418466
"JSON": {jsonRaw: json.RawMessage(`{"pushInterval":"1m2s"}`)},
419467
"Env": {env: map[string]string{"K6_PROMETHEUS_RW_PUSH_INTERVAL": "1m2s"}},
420-
//nolint:gocritic
421-
//"Arg": {arg: "pushInterval=1m2s"},
422468
}
423469

424470
expconfig := Config{

0 commit comments

Comments
 (0)