Skip to content
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

TLS certificates #143

Merged
merged 16 commits into from
Sep 25, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 39 additions & 0 deletions pkg/remotewrite/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,16 @@ type Config struct {
// Password is the Password for the Basic Auth.
Password null.String `json:"password"`

// ClientCertificate is the public key of the SSL certificate.
olegbespalov marked this conversation as resolved.
Show resolved Hide resolved
// It is expected the path of the certificate on the file system.
// If it is required a dedicated Certifacate Authority then it should be added
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It theory we could also provide the way to add the CA like it's implemented in k6, but not sure if it's under the scope of this PR

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would prefer if we get an explicit request from users for it. The demand for mTLS feature seems to be not so high in general.

// to the conventional folders defined by the operating system's registry.
ClientCertificate null.String `json:"clientCertificate"`

// ClientCertificateKey is the private key of the SSL certificate.
// It is expected the path of the certificate on the file system.
ClientCertificateKey null.String `json:"clientCertificateKey"`

// BearerToken if set is the token used for the `Authorization` header.
BearerToken null.String `json:"bearerToken"`

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

if conf.ClientCertificate.Valid && conf.ClientCertificateKey.Valid {
cert, err := tls.LoadX509KeyPair(conf.ClientCertificate.String, conf.ClientCertificateKey.String)
if err != nil {
return nil, fmt.Errorf("failed to load the TLS certificate: %w", err)
}
hc.TLSConfig.Certificates = []tls.Certificate{cert}
}

if len(conf.Headers) > 0 {
hc.Headers = make(http.Header)
for k, v := range conf.Headers {
Expand Down Expand Up @@ -150,6 +168,14 @@ func (conf Config) Apply(applied Config) Config {
copy(conf.TrendStats, applied.TrendStats)
}

if applied.ClientCertificate.Valid {
conf.ClientCertificate = applied.ClientCertificate
}

if applied.ClientCertificateKey.Valid {
conf.ClientCertificateKey = applied.ClientCertificateKey
}

return conf
}

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

if clientCertificate, certDefined := env["K6_PROMETHEUS_RW_CLIENT_CERTIFICATE"]; certDefined {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if clientCertificate, certDefined := env["K6_PROMETHEUS_RW_CLIENT_CERTIFICATE"]; certDefined {
if clientCertificate, certDefined := env["K6_PROMETHEUS_RW_TLS_CLIENT_CERTIFICATE"]; certDefined {

Do you think we should be more specific about the naming?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Both fine fro me 🤷

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there something else that we can have a client certificate for ?

If not I am also 🤷 - if yes , then I would prefer to have mtls even.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not really, or at least I didn't find it. At the moment, if we mention client certificates then they should set automatically the context for TLS, but I can't guarantee for the future.

Copy link
Contributor Author

@codebien codebien Sep 25, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We are doing mostly the same with Username and Password where we aren't specifying that is for BasicAuth.

c.ClientCertificate = null.StringFrom(clientCertificate)
}

if clientCertificateKey, certDefined := env["K6_PROMETHEUS_RW_CLIENT_CERTIFICATE_KEY"]; certDefined {
c.ClientCertificateKey = null.StringFrom(clientCertificateKey)
}

envHeaders := getEnvMap(env, "K6_PROMETHEUS_RW_HEADERS_")
for k, v := range envHeaders {
c.Headers[k] = v
Expand Down Expand Up @@ -324,6 +358,11 @@ func parseArg(text string) (Config, error) {
//c.TrendStats = strings.Split(v, ",")
//}

case "clientCertificate":
c.ClientCertificate = null.StringFrom(v)
case "clientCertificateKey":
c.ClientCertificateKey = null.StringFrom(v)

default:
if !strings.HasPrefix(key, "headers.") {
return c, fmt.Errorf("%q is an unknown option's key", r[0])
Expand Down
50 changes: 48 additions & 2 deletions pkg/remotewrite/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,19 @@ func TestConfigRemoteConfig(t *testing.T) {
assert.Equal(t, exprcc, rcc)
}

func TestConfigRemoteConfigClientCertificateError(t *testing.T) {
t.Parallel()

config := Config{
ClientCertificate: null.StringFrom("bad-cert-value"),
ClientCertificateKey: null.StringFrom("bad-cert-key"),
}

rcc, err := config.RemoteConfig()
assert.ErrorContains(t, err, "TLS certificate")
assert.Nil(t, rcc)
}

func TestGetConsolidatedConfig(t *testing.T) {
t.Parallel()

Expand Down Expand Up @@ -369,6 +382,41 @@ func TestOptionBasicAuth(t *testing.T) {
}
}

func TestOptionClientCertificate(t *testing.T) {
t.Parallel()

cases := map[string]struct {
arg string
env map[string]string
jsonRaw json.RawMessage
}{
"JSON": {jsonRaw: json.RawMessage(`{"clientCertificate":"client.crt","clientCertificateKey":"client.key"}`)},
"Env": {env: map[string]string{"K6_PROMETHEUS_RW_CLIENT_CERTIFICATE": "client.crt", "K6_PROMETHEUS_RW_CLIENT_CERTIFICATE_KEY": "client.key"}},
}

expconfig := Config{
ServerURL: null.StringFrom("http://localhost:9090/api/v1/write"),
InsecureSkipTLSVerify: null.BoolFrom(false),
PushInterval: types.NullDurationFrom(5 * time.Second),
Headers: make(map[string]string),
TrendStats: []string{"p(99)"},
ClientCertificate: null.StringFrom("client.crt"),
ClientCertificateKey: null.StringFrom("client.key"),
StaleMarkers: null.BoolFrom(false),
}

for name, tc := range cases {
tc := tc
t.Run(name, func(t *testing.T) {
t.Parallel()
c, err := GetConsolidatedConfig(
tc.jsonRaw, tc.env, tc.arg)
require.NoError(t, err)
assert.Equal(t, expconfig, c)
})
}
}

func TestOptionTrendAsNativeHistogram(t *testing.T) {
t.Parallel()

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

expconfig := Config{
Expand Down