From abbd1a8c199b221e49d3209f0e6c83c78ee82228 Mon Sep 17 00:00:00 2001 From: Chris Moran Date: Sun, 2 Jul 2023 07:49:57 -0400 Subject: [PATCH 01/24] ### Support for per-vu TLS configuration (grpc.Client.connect only) and custom (self-signed) RootCAs You can now use self-signed ca-certificates when authenticating with TLS. Using the `cacerts` property of an options `tlsAuth` object, you can now indicate the CA certificate(s) that k6 will use to verify the server certificates your tests will access. CA certificates must be in `pem` format and multiple CA certificates can be included. ```javascript export const options = { tlsAuth: [ { cacerts: [open("cacerts.pem")], cert: open("cert.pem"), key: open("cert-key.pem"), password: "cert-passphrase", domains: ["grpcbin.test.k6.io"], // Deprecated }, ], }; ``` You can also now use per-vu TLS configuration on `grpc.Client` `connect` params. Previously, `grpc.Client` `connect` made use of a shared (immutable) `tls.Config` cloned into every VU. Now, the `connect` `params` argument can contain a `tlsconfig` key that maps to the `TLSAuth` struct. This will create a per-vu TLS configuration for the `grpc.Client` allowing you to connect to multiple mTLS endpoints in a single VU. Note: the global tls.Config will remain unmodified. ```javascript const params = { plaintext: false, tlsconfig: { cacerts: ["cacerts.pem"], cert: "cert.pem", key: "key.pem", password: "cert-passphrase", domains: ["grpcbin.test.notk6.io"], // Deprecated } }; const hostPort = "grpcbin.test.notk6.io:9001"; const client = new grpc.Client(); client.connect(hostPort, params); ``` --- js/modules/k6/execution/execution_test.go | 3 +- js/modules/k6/grpc/client.go | 87 ++++++++++++++++++++++- js/runner.go | 7 ++ lib/options.go | 18 +++++ 4 files changed, 112 insertions(+), 3 deletions(-) diff --git a/js/modules/k6/execution/execution_test.go b/js/modules/k6/execution/execution_test.go index 615d0f7f232..5ff14ff0dd5 100644 --- a/js/modules/k6/execution/execution_test.go +++ b/js/modules/k6/execution/execution_test.go @@ -223,7 +223,7 @@ func TestAbortTest(t *testing.T) { //nolint:tparallel func TestOptionsTestFull(t *testing.T) { t.Parallel() - expected := `{"paused":true,"scenarios":{"const-vus":{"executor":"constant-vus","options":{"browser":{"someOption":true}},"startTime":"10s","gracefulStop":"30s","env":{"FOO":"bar"},"exec":"default","tags":{"tagkey":"tagvalue"},"vus":50,"duration":"10m0s"}},"executionSegment":"0:1/4","executionSegmentSequence":"0,1/4,1/2,1","noSetup":true,"setupTimeout":"1m0s","noTeardown":true,"teardownTimeout":"5m0s","rps":100,"dns":{"ttl":"1m","select":"roundRobin","policy":"any"},"maxRedirects":3,"userAgent":"k6-user-agent","batch":15,"batchPerHost":5,"httpDebug":"full","insecureSkipTLSVerify":true,"tlsCipherSuites":["TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256"],"tlsVersion":{"min":"tls1.2","max":"tls1.3"},"tlsAuth":[{"domains":["example.com"],"cert":"mycert.pem","key":"mycert-key.pem","password":"mypwd"}],"throw":true,"thresholds":{"http_req_duration":[{"threshold":"rate>0.01","abortOnFail":true,"delayAbortEval":"10s"}]},"blacklistIPs":["192.0.2.0/24"],"blockHostnames":["test.k6.io","*.example.com"],"hosts":{"test.k6.io":"1.2.3.4:8443"},"noConnectionReuse":true,"noVUConnectionReuse":true,"minIterationDuration":"10s","ext":{"ext-one":{"rawkey":"rawvalue"}},"summaryTrendStats":["avg","min","max"],"summaryTimeUnit":"ms","systemTags":["iter","vu"],"tags":null,"metricSamplesBufferSize":8,"noCookiesReset":true,"discardResponseBodies":true,"consoleOutput":"loadtest.log","tags":{"runtag-key":"runtag-value"},"localIPs":"192.168.20.12-192.168.20.15,192.168.10.0/27"}` + expected := `{"paused":true,"scenarios":{"const-vus":{"executor":"constant-vus","options":{"browser":{"someOption":true}},"startTime":"10s","gracefulStop":"30s","env":{"FOO":"bar"},"exec":"default","tags":{"tagkey":"tagvalue"},"vus":50,"duration":"10m0s"}},"executionSegment":"0:1/4","executionSegmentSequence":"0,1/4,1/2,1","noSetup":true,"setupTimeout":"1m0s","noTeardown":true,"teardownTimeout":"5m0s","rps":100,"dns":{"ttl":"1m","select":"roundRobin","policy":"any"},"maxRedirects":3,"userAgent":"k6-user-agent","batch":15,"batchPerHost":5,"httpDebug":"full","insecureSkipTLSVerify":true,"tlsCipherSuites":["TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256"],"tlsVersion":{"min":"tls1.2","max":"tls1.3"},"tlsAuth":[{"cacerts":[],"domains":["example.com"],"cert":"mycert.pem","key":"mycert-key.pem","password":"mypwd"}],"throw":true,"thresholds":{"http_req_duration":[{"threshold":"rate>0.01","abortOnFail":true,"delayAbortEval":"10s"}]},"blacklistIPs":["192.0.2.0/24"],"blockHostnames":["test.k6.io","*.example.com"],"hosts":{"test.k6.io":"1.2.3.4:8443"},"noConnectionReuse":true,"noVUConnectionReuse":true,"minIterationDuration":"10s","ext":{"ext-one":{"rawkey":"rawvalue"}},"summaryTrendStats":["avg","min","max"],"summaryTimeUnit":"ms","systemTags":["iter","vu"],"tags":null,"metricSamplesBufferSize":8,"noCookiesReset":true,"discardResponseBodies":true,"consoleOutput":"loadtest.log","tags":{"runtag-key":"runtag-value"},"localIPs":"192.168.20.12-192.168.20.15,192.168.10.0/27"}` var ( rt = goja.New() @@ -294,6 +294,7 @@ func TestOptionsTestFull(t *testing.T) { TLSAuth: []*lib.TLSAuth{ { TLSAuthFields: lib.TLSAuthFields{ + CACerts: []string{}, Cert: "mycert.pem", Key: "mycert-key.pem", Password: null.StringFrom("mypwd"), diff --git a/js/modules/k6/grpc/client.go b/js/modules/k6/grpc/client.go index 1a9538a7df4..12ea3f95f96 100644 --- a/js/modules/k6/grpc/client.go +++ b/js/modules/k6/grpc/client.go @@ -2,6 +2,8 @@ package grpc import ( "context" + "crypto/tls" + "crypto/x509" "errors" "fmt" "io" @@ -108,6 +110,51 @@ func (c *Client) LoadProtoset(protosetPath string) ([]MethodInfo, error) { return c.convertToMethodInfo(fdset) } +func buildTLSConfig(certificate, key []byte, caCertificates [][]byte) (*tls.Config, error) { + var cp *x509.CertPool + var err error + if cp, err = x509.SystemCertPool(); err != nil { + return nil, err + } + if len(caCertificates) > 0 { + for i, caCert := range caCertificates { + if ok := cp.AppendCertsFromPEM(caCert); !ok { + return nil, fmt.Errorf("failed to append ca certificate [%d] from PEM", i) + } + } + } + cert, certerr := tls.X509KeyPair(certificate, key) + if certerr != nil { + return nil, fmt.Errorf("failed to append certificate from PEM") + } + return &tls.Config{ + RootCAs: cp, + Certificates: []tls.Certificate{cert}, + MinVersion: tls.VersionTLS12, + }, nil +} + +func buildTLSConfigFromMap(tlsConfigMap map[string]interface{}) (*tls.Config, error) { + var cert, key []byte + var ca [][]byte + if certstr, ok := tlsConfigMap["cert"].(string); ok { + cert = []byte(certstr) + } + if keystr, ok := tlsConfigMap["key"].(string); ok { + key = []byte(keystr) + } + cas := tlsConfigMap["cacerts"] + ca = make([][]byte, 0) + if casarr, ok := cas.([]string); ok { + for _, casEntry := range casarr { + ca = append(ca, []byte(casEntry)) + } + } else if casstr, casstrok := cas.(string); casstrok { + ca = append(ca, []byte(casstr)) + } + return buildTLSConfig(cert, key, ca) +} + // Connect is a block dial to the gRPC server at the given address (host:port) func (c *Client) Connect(addr string, params map[string]interface{}) (bool, error) { state := c.vu.State() @@ -124,10 +171,16 @@ func (c *Client) Connect(addr string, params map[string]interface{}) (bool, erro var tcred credentials.TransportCredentials if !p.IsPlaintext { - tlsCfg := state.TLSConfig.Clone() + var tlsCfg *tls.Config + if len(p.TLSConfig) > 0 { + if tlsCfg, err = buildTLSConfigFromMap(p.TLSConfig); err != nil { + return false, err + } + } else { + tlsCfg = state.TLSConfig.Clone() + } tlsCfg.NextProtos = []string{"h2"} - // TODO(rogchap): Would be good to add support for custom RootCAs (self signed) tcred = credentials.NewTLS(tlsCfg) } else { tcred = insecure.NewCredentials() @@ -373,6 +426,7 @@ type connectParams struct { Timeout time.Duration MaxReceiveSize int64 MaxSendSize int64 + TLSConfig map[string]interface{} } func (c *Client) parseConnectParams(raw map[string]interface{}) (connectParams, error) { @@ -382,6 +436,7 @@ func (c *Client) parseConnectParams(raw map[string]interface{}) (connectParams, Timeout: time.Minute, MaxReceiveSize: 0, MaxSendSize: 0, + TLSConfig: make(map[string]interface{}), } for k, v := range raw { switch k { @@ -421,7 +476,35 @@ func (c *Client) parseConnectParams(raw map[string]interface{}) (connectParams, if params.MaxSendSize < 0 { return params, fmt.Errorf("invalid maxSendSize value: '%#v, it needs to be a positive integer", v) } + case "tlsconfig": + var ok bool + params.TLSConfig, ok = v.(map[string]interface{}) + err := fmt.Errorf("invalid tlsconfig value: '%#v', tlsconfig needs cert, key, and (optionally) cacerts", v) + if !ok { + return params, err + } + if cert, certok := params.TLSConfig["cert"]; certok { + if _, ok = cert.(string); !ok { + return params, err + } + } else { + return params, err + } + if key, keyok := params.TLSConfig["key"]; keyok { + if _, ok = key.(string); !ok { + return params, err + } + } else { + return params, err + } + if cacerts, cacertsok := params.TLSConfig["cacerts"]; cacertsok { + if _, ok = cacerts.([]string); !ok { + if _, ok = cacerts.(string); !ok { + return params, err + } + } + } default: return params, fmt.Errorf("unknown connect param: %q", k) } diff --git a/js/runner.go b/js/runner.go index 977bd798017..bd28fc54128 100644 --- a/js/runner.go +++ b/js/runner.go @@ -3,6 +3,7 @@ package js import ( "context" "crypto/tls" + "crypto/x509" "encoding/json" "errors" "fmt" @@ -148,11 +149,16 @@ func (r *Runner) newVU( tlsAuth := r.Bundle.Options.TLSAuth certs := make([]tls.Certificate, len(tlsAuth)) nameToCert := make(map[string]*tls.Certificate) + var rootCAs *x509.CertPool for i, auth := range tlsAuth { cert, errC := auth.Certificate() if errC != nil { return nil, errC } + errC = auth.RootCAs(rootCAs) + if errC != nil { + return nil, errC + } certs[i] = *cert for _, name := range auth.Domains { nameToCert[name] = cert @@ -175,6 +181,7 @@ func (r *Runner) newVU( } tlsConfig := &tls.Config{ + RootCAs: rootCAs, InsecureSkipVerify: r.Bundle.Options.InsecureSkipTLSVerify.Bool, //nolint:gosec CipherSuites: cipherSuites, MinVersion: uint16(tlsVersions.Min), diff --git a/lib/options.go b/lib/options.go index 5baad062b8f..b06245d93ae 100644 --- a/lib/options.go +++ b/lib/options.go @@ -111,6 +111,9 @@ func (s *TLSCipherSuites) UnmarshalJSON(data []byte) error { // Fields for TLSAuth. Unmarshalling hack. type TLSAuthFields struct { + // CACerts as PEM-encoded string(s), including "-----BEGIN CERTIFICATE-----". + CACerts []string `json:"cacerts"` + // Certificate and key as a PEM-encoded string, including "-----BEGIN CERTIFICATE-----". Cert string `json:"cert"` Key string `json:"key"` @@ -136,6 +139,21 @@ func (c *TLSAuth) UnmarshalJSON(data []byte) error { return nil } +// RootCAs appends certs from input CACerts to the input cert pool +func (c *TLSAuth) RootCAs(rootCAs *x509.CertPool) error { + if c.CACerts != nil && len(c.CACerts) > 0 { + if rootCAs == nil { + rootCAs = x509.NewCertPool() + } + for _, caCert := range c.CACerts { + if ok := rootCAs.AppendCertsFromPEM([]byte(caCert)); !ok { + return fmt.Errorf("unable to load CA Certificate PEM") + } + } + } + return nil +} + func (c *TLSAuth) Certificate() (*tls.Certificate, error) { key := []byte(c.Key) var err error From 2c3e1c8d1b9c1dce94f5d783481c21875543f283 Mon Sep 17 00:00:00 2001 From: Chris Moran Date: Tue, 4 Jul 2023 08:01:42 -0400 Subject: [PATCH 02/24] Fixed cast when cacerts is passed as a string[] --- js/modules/k6/grpc/client.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/modules/k6/grpc/client.go b/js/modules/k6/grpc/client.go index 12ea3f95f96..0b3ceafff60 100644 --- a/js/modules/k6/grpc/client.go +++ b/js/modules/k6/grpc/client.go @@ -499,7 +499,7 @@ func (c *Client) parseConnectParams(raw map[string]interface{}) (connectParams, return params, err } if cacerts, cacertsok := params.TLSConfig["cacerts"]; cacertsok { - if _, ok = cacerts.([]string); !ok { + if _, ok = cacerts.([]interface{}); !ok { if _, ok = cacerts.(string); !ok { return params, err } From 2da60cb110746462ed2e110599075573cdc6db91 Mon Sep 17 00:00:00 2001 From: Chris Moran Date: Tue, 4 Jul 2023 10:05:56 -0400 Subject: [PATCH 03/24] Remove CACerts change from TLSAuthFields per PR request --- js/runner.go | 7 ------- lib/options.go | 18 ------------------ 2 files changed, 25 deletions(-) diff --git a/js/runner.go b/js/runner.go index bd28fc54128..977bd798017 100644 --- a/js/runner.go +++ b/js/runner.go @@ -3,7 +3,6 @@ package js import ( "context" "crypto/tls" - "crypto/x509" "encoding/json" "errors" "fmt" @@ -149,16 +148,11 @@ func (r *Runner) newVU( tlsAuth := r.Bundle.Options.TLSAuth certs := make([]tls.Certificate, len(tlsAuth)) nameToCert := make(map[string]*tls.Certificate) - var rootCAs *x509.CertPool for i, auth := range tlsAuth { cert, errC := auth.Certificate() if errC != nil { return nil, errC } - errC = auth.RootCAs(rootCAs) - if errC != nil { - return nil, errC - } certs[i] = *cert for _, name := range auth.Domains { nameToCert[name] = cert @@ -181,7 +175,6 @@ func (r *Runner) newVU( } tlsConfig := &tls.Config{ - RootCAs: rootCAs, InsecureSkipVerify: r.Bundle.Options.InsecureSkipTLSVerify.Bool, //nolint:gosec CipherSuites: cipherSuites, MinVersion: uint16(tlsVersions.Min), diff --git a/lib/options.go b/lib/options.go index b06245d93ae..5baad062b8f 100644 --- a/lib/options.go +++ b/lib/options.go @@ -111,9 +111,6 @@ func (s *TLSCipherSuites) UnmarshalJSON(data []byte) error { // Fields for TLSAuth. Unmarshalling hack. type TLSAuthFields struct { - // CACerts as PEM-encoded string(s), including "-----BEGIN CERTIFICATE-----". - CACerts []string `json:"cacerts"` - // Certificate and key as a PEM-encoded string, including "-----BEGIN CERTIFICATE-----". Cert string `json:"cert"` Key string `json:"key"` @@ -139,21 +136,6 @@ func (c *TLSAuth) UnmarshalJSON(data []byte) error { return nil } -// RootCAs appends certs from input CACerts to the input cert pool -func (c *TLSAuth) RootCAs(rootCAs *x509.CertPool) error { - if c.CACerts != nil && len(c.CACerts) > 0 { - if rootCAs == nil { - rootCAs = x509.NewCertPool() - } - for _, caCert := range c.CACerts { - if ok := rootCAs.AppendCertsFromPEM([]byte(caCert)); !ok { - return fmt.Errorf("unable to load CA Certificate PEM") - } - } - } - return nil -} - func (c *TLSAuth) Certificate() (*tls.Certificate, error) { key := []byte(c.Key) var err error From 748bbf3a31822c48766136d289cc83cf56c34de8 Mon Sep 17 00:00:00 2001 From: Chris Moran Date: Tue, 4 Jul 2023 11:35:55 -0400 Subject: [PATCH 04/24] Fix execution_test after removing CACerts from tlsAuth option --- js/modules/k6/execution/execution_test.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/js/modules/k6/execution/execution_test.go b/js/modules/k6/execution/execution_test.go index 5ff14ff0dd5..615d0f7f232 100644 --- a/js/modules/k6/execution/execution_test.go +++ b/js/modules/k6/execution/execution_test.go @@ -223,7 +223,7 @@ func TestAbortTest(t *testing.T) { //nolint:tparallel func TestOptionsTestFull(t *testing.T) { t.Parallel() - expected := `{"paused":true,"scenarios":{"const-vus":{"executor":"constant-vus","options":{"browser":{"someOption":true}},"startTime":"10s","gracefulStop":"30s","env":{"FOO":"bar"},"exec":"default","tags":{"tagkey":"tagvalue"},"vus":50,"duration":"10m0s"}},"executionSegment":"0:1/4","executionSegmentSequence":"0,1/4,1/2,1","noSetup":true,"setupTimeout":"1m0s","noTeardown":true,"teardownTimeout":"5m0s","rps":100,"dns":{"ttl":"1m","select":"roundRobin","policy":"any"},"maxRedirects":3,"userAgent":"k6-user-agent","batch":15,"batchPerHost":5,"httpDebug":"full","insecureSkipTLSVerify":true,"tlsCipherSuites":["TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256"],"tlsVersion":{"min":"tls1.2","max":"tls1.3"},"tlsAuth":[{"cacerts":[],"domains":["example.com"],"cert":"mycert.pem","key":"mycert-key.pem","password":"mypwd"}],"throw":true,"thresholds":{"http_req_duration":[{"threshold":"rate>0.01","abortOnFail":true,"delayAbortEval":"10s"}]},"blacklistIPs":["192.0.2.0/24"],"blockHostnames":["test.k6.io","*.example.com"],"hosts":{"test.k6.io":"1.2.3.4:8443"},"noConnectionReuse":true,"noVUConnectionReuse":true,"minIterationDuration":"10s","ext":{"ext-one":{"rawkey":"rawvalue"}},"summaryTrendStats":["avg","min","max"],"summaryTimeUnit":"ms","systemTags":["iter","vu"],"tags":null,"metricSamplesBufferSize":8,"noCookiesReset":true,"discardResponseBodies":true,"consoleOutput":"loadtest.log","tags":{"runtag-key":"runtag-value"},"localIPs":"192.168.20.12-192.168.20.15,192.168.10.0/27"}` + expected := `{"paused":true,"scenarios":{"const-vus":{"executor":"constant-vus","options":{"browser":{"someOption":true}},"startTime":"10s","gracefulStop":"30s","env":{"FOO":"bar"},"exec":"default","tags":{"tagkey":"tagvalue"},"vus":50,"duration":"10m0s"}},"executionSegment":"0:1/4","executionSegmentSequence":"0,1/4,1/2,1","noSetup":true,"setupTimeout":"1m0s","noTeardown":true,"teardownTimeout":"5m0s","rps":100,"dns":{"ttl":"1m","select":"roundRobin","policy":"any"},"maxRedirects":3,"userAgent":"k6-user-agent","batch":15,"batchPerHost":5,"httpDebug":"full","insecureSkipTLSVerify":true,"tlsCipherSuites":["TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256"],"tlsVersion":{"min":"tls1.2","max":"tls1.3"},"tlsAuth":[{"domains":["example.com"],"cert":"mycert.pem","key":"mycert-key.pem","password":"mypwd"}],"throw":true,"thresholds":{"http_req_duration":[{"threshold":"rate>0.01","abortOnFail":true,"delayAbortEval":"10s"}]},"blacklistIPs":["192.0.2.0/24"],"blockHostnames":["test.k6.io","*.example.com"],"hosts":{"test.k6.io":"1.2.3.4:8443"},"noConnectionReuse":true,"noVUConnectionReuse":true,"minIterationDuration":"10s","ext":{"ext-one":{"rawkey":"rawvalue"}},"summaryTrendStats":["avg","min","max"],"summaryTimeUnit":"ms","systemTags":["iter","vu"],"tags":null,"metricSamplesBufferSize":8,"noCookiesReset":true,"discardResponseBodies":true,"consoleOutput":"loadtest.log","tags":{"runtag-key":"runtag-value"},"localIPs":"192.168.20.12-192.168.20.15,192.168.10.0/27"}` var ( rt = goja.New() @@ -294,7 +294,6 @@ func TestOptionsTestFull(t *testing.T) { TLSAuth: []*lib.TLSAuth{ { TLSAuthFields: lib.TLSAuthFields{ - CACerts: []string{}, Cert: "mycert.pem", Key: "mycert-key.pem", Password: null.StringFrom("mypwd"), From 044e17674c260f2e5e0117ff47688e054c6720a3 Mon Sep 17 00:00:00 2001 From: Chris Moran Date: Tue, 4 Jul 2023 11:48:24 -0400 Subject: [PATCH 05/24] Added tests for feature/per-vu-grpc-tls-config-with-rootcas --- js/modules/k6/grpc/client.go | 32 +++++++------ js/modules/k6/grpc/client_test.go | 47 +++++++++++++++++++ lib/testutils/httpmultibin/httpmultibin.go | 52 ++++++++++++++++++++++ 3 files changed, 114 insertions(+), 17 deletions(-) diff --git a/js/modules/k6/grpc/client.go b/js/modules/k6/grpc/client.go index 0b3ceafff60..d0af1431a14 100644 --- a/js/modules/k6/grpc/client.go +++ b/js/modules/k6/grpc/client.go @@ -112,11 +112,8 @@ func (c *Client) LoadProtoset(protosetPath string) ([]MethodInfo, error) { func buildTLSConfig(certificate, key []byte, caCertificates [][]byte) (*tls.Config, error) { var cp *x509.CertPool - var err error - if cp, err = x509.SystemCertPool(); err != nil { - return nil, err - } if len(caCertificates) > 0 { + cp, _ = x509.SystemCertPool() for i, caCert := range caCertificates { if ok := cp.AppendCertsFromPEM(caCert); !ok { return nil, fmt.Errorf("failed to append ca certificate [%d] from PEM", i) @@ -172,8 +169,8 @@ func (c *Client) Connect(addr string, params map[string]interface{}) (bool, erro var tcred credentials.TransportCredentials if !p.IsPlaintext { var tlsCfg *tls.Config - if len(p.TLSConfig) > 0 { - if tlsCfg, err = buildTLSConfigFromMap(p.TLSConfig); err != nil { + if len(p.TLS) > 0 { + if tlsCfg, err = buildTLSConfigFromMap(p.TLS); err != nil { return false, err } } else { @@ -426,7 +423,7 @@ type connectParams struct { Timeout time.Duration MaxReceiveSize int64 MaxSendSize int64 - TLSConfig map[string]interface{} + TLS map[string]interface{} } func (c *Client) parseConnectParams(raw map[string]interface{}) (connectParams, error) { @@ -436,7 +433,7 @@ func (c *Client) parseConnectParams(raw map[string]interface{}) (connectParams, Timeout: time.Minute, MaxReceiveSize: 0, MaxSendSize: 0, - TLSConfig: make(map[string]interface{}), + TLS: make(map[string]interface{}), } for k, v := range raw { switch k { @@ -476,32 +473,33 @@ func (c *Client) parseConnectParams(raw map[string]interface{}) (connectParams, if params.MaxSendSize < 0 { return params, fmt.Errorf("invalid maxSendSize value: '%#v, it needs to be a positive integer", v) } - case "tlsconfig": + case "tls": var ok bool - params.TLSConfig, ok = v.(map[string]interface{}) - err := fmt.Errorf("invalid tlsconfig value: '%#v', tlsconfig needs cert, key, and (optionally) cacerts", v) + params.TLS, ok = v.(map[string]interface{}) + err := fmt.Errorf("invalid tls value: '%#v', tls needs cert, key, and (optionally) cacerts", v) if !ok { return params, err } - if cert, certok := params.TLSConfig["cert"]; certok { + if cert, certok := params.TLS["cert"]; certok { if _, ok = cert.(string); !ok { - return params, err + return params, fmt.Errorf("invalid tls cert value: '%#v', it needs to be a PEM formatted string", v) } } else { return params, err } - if key, keyok := params.TLSConfig["key"]; keyok { + if key, keyok := params.TLS["key"]; keyok { if _, ok = key.(string); !ok { - return params, err + return params, fmt.Errorf("invalid tls key value: '%#v', it needs to be a PEM formatted string", v) } } else { return params, err } - if cacerts, cacertsok := params.TLSConfig["cacerts"]; cacertsok { + if cacerts, cacertsok := params.TLS["cacerts"]; cacertsok { if _, ok = cacerts.([]interface{}); !ok { if _, ok = cacerts.(string); !ok { - return params, err + return params, fmt.Errorf("invalid tls cacerts value: '%#v',"+ + " it needs to be a string or string[] of PEM formatted strings", v) } } } diff --git a/js/modules/k6/grpc/client_test.go b/js/modules/k6/grpc/client_test.go index e19e65d6527..aa8ea3b3f96 100644 --- a/js/modules/k6/grpc/client_test.go +++ b/js/modules/k6/grpc/client_test.go @@ -261,6 +261,53 @@ func TestClient(t *testing.T) { client.load([], "../../../../lib/testutils/httpmultibin/grpc_testing/test.proto");`}, vuString: codeBlock{code: `client.connect("GRPCBIN_ADDR", { timeout: 3456.3 });`}, }, + { + name: "ConnectTlsInvalidEmptyTls", + initString: codeBlock{code: ` + var client = new grpc.Client(); + client.load([], "../../../../lib/testutils/httpmultibin/grpc_testing/test.proto");`}, + vuString: codeBlock{ + code: `client.connect("GRPCBIN_ADDR", { tls: { }});`, + err: "invalid grpc.connect() parameters: invalid tls value: 'map[string]interface {}{}', tls needs cert, key, and (optionally) cacerts", + }, + }, + { + name: "ConnectTlsInvalidTlsParamCertType", + initString: codeBlock{code: ` + var client = new grpc.Client(); + client.load([], "../../../../lib/testutils/httpmultibin/grpc_testing/test.proto");`}, + vuString: codeBlock{ + code: `client.connect("GRPCBIN_ADDR", { tls: { cert: 0 }});`, + err: `invalid grpc.connect() parameters: invalid tls cert value: 'map[string]interface {}{"cert":0}', it needs to be a PEM formatted string`, + }, + }, + { + name: "ConnectTlsInvalidTlsParamKeyType", + initString: codeBlock{code: ` + var client = new grpc.Client(); + client.load([], "../../../../lib/testutils/httpmultibin/grpc_testing/test.proto");`}, + vuString: codeBlock{ + code: `client.connect("GRPCBIN_ADDR", { tls: { cert: "", key: 0 }});`, + err: `invalid grpc.connect() parameters: invalid tls key value: 'map[string]interface {}{"cert":"", "key":0}', it needs to be a PEM formatted string`, + }, + }, + { + name: "ConnectTlsInvalidTlsParamCACertsType", + initString: codeBlock{code: ` + var client = new grpc.Client(); + client.load([], "../../../../lib/testutils/httpmultibin/grpc_testing/test.proto");`}, + vuString: codeBlock{ + code: `client.connect("GRPCBIN_ADDR", { tls: { cert: "", key: "", cacerts: 0 }});`, + err: `invalid grpc.connect() parameters: invalid tls cacerts value: 'map[string]interface {}{"cacerts":0, "cert":"", "key":""}', it needs to be a string or string[] of PEM formatted strings`, + }, + }, + { + name: "ConnectTls", + initString: codeBlock{code: ` + var client = new grpc.Client(); + client.load([], "../../../../lib/testutils/httpmultibin/grpc_testing/test.proto");`}, + vuString: codeBlock{code: `client.connect("GRPCBIN_ADDR", { tls: { cacerts: "LOCALHOST_CERT", cert: "LOCALHOST_CERT", key: "LOCALHOST_KEY" }});`}, + }, { name: "Connect", initString: codeBlock{code: ` diff --git a/lib/testutils/httpmultibin/httpmultibin.go b/lib/testutils/httpmultibin/httpmultibin.go index fac0cc3699f..81e3058a854 100644 --- a/lib/testutils/httpmultibin/httpmultibin.go +++ b/lib/testutils/httpmultibin/httpmultibin.go @@ -67,6 +67,56 @@ const httpDomain = "httpbin.local" // https://golang.org/src/net/http/internal/testcert.go?s=399:410#L10 const httpsDomain = "example.com" +const localhostCert = `-----BEGIN CERTIFICATE----- +MIIDOTCCAiGgAwIBAgIQSRJrEpBGFc7tNb1fb5pKFzANBgkqhkiG9w0BAQsFADAS +MRAwDgYDVQQKEwdBY21lIENvMCAXDTcwMDEwMTAwMDAwMFoYDzIwODQwMTI5MTYw +MDAwWjASMRAwDgYDVQQKEwdBY21lIENvMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A +MIIBCgKCAQEA6Gba5tHV1dAKouAaXO3/ebDUU4rvwCUg/CNaJ2PT5xLD4N1Vcb8r +bFSW2HXKq+MPfVdwIKR/1DczEoAGf/JWQTW7EgzlXrCd3rlajEX2D73faWJekD0U +aUgz5vtrTXZ90BQL7WvRICd7FlEZ6FPOcPlumiyNmzUqtwGhO+9ad1W5BqJaRI6P +YfouNkwR6Na4TzSj5BrqUfP0FwDizKSJ0XXmh8g8G9mtwxOSN3Ru1QFc61Xyeluk +POGKBV/q6RBNklTNe0gI8usUMlYyoC7ytppNMW7X2vodAelSu25jgx2anj9fDVZu +h7AXF5+4nJS4AAt0n1lNY7nGSsdZas8PbQIDAQABo4GIMIGFMA4GA1UdDwEB/wQE +AwICpDATBgNVHSUEDDAKBggrBgEFBQcDATAPBgNVHRMBAf8EBTADAQH/MB0GA1Ud +DgQWBBStsdjh3/JCXXYlQryOrL4Sh7BW5TAuBgNVHREEJzAlggtleGFtcGxlLmNv +bYcEfwAAAYcQAAAAAAAAAAAAAAAAAAAAATANBgkqhkiG9w0BAQsFAAOCAQEAxWGI +5NhpF3nwwy/4yB4i/CwwSpLrWUa70NyhvprUBC50PxiXav1TeDzwzLx/o5HyNwsv +cxv3HdkLW59i/0SlJSrNnWdfZ19oTcS+6PtLoVyISgtyN6DpkKpdG1cOkW3Cy2P2 ++tK/tKHRP1Y/Ra0RiDpOAmqn0gCOFGz8+lqDIor/T7MTpibL3IxqWfPrvfVRHL3B +grw/ZQTTIVjjh4JBSW3WyWgNo/ikC1lrVxzl4iPUGptxT36Cr7Zk2Bsg0XqwbOvK +5d+NTDREkSnUbie4GeutujmX3Dsx88UiV6UY/4lHJa6I5leHUNOHahRbpbWeOfs/ +WkBKOclmOV2xlTVuPw== +-----END CERTIFICATE-----` + +const localhostKey = `-----BEGIN RSA PRIVATE KEY----- +MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQDoZtrm0dXV0Aqi +4Bpc7f95sNRTiu/AJSD8I1onY9PnEsPg3VVxvytsVJbYdcqr4w99V3AgpH/UNzMS +gAZ/8lZBNbsSDOVesJ3euVqMRfYPvd9pYl6QPRRpSDPm+2tNdn3QFAvta9EgJ3sW +URnoU85w+W6aLI2bNSq3AaE771p3VbkGolpEjo9h+i42TBHo1rhPNKPkGupR8/QX +AOLMpInRdeaHyDwb2a3DE5I3dG7VAVzrVfJ6W6Q84YoFX+rpEE2SVM17SAjy6xQy +VjKgLvK2mk0xbtfa+h0B6VK7bmODHZqeP18NVm6HsBcXn7iclLgAC3SfWU1jucZK +x1lqzw9tAgMBAAECggEABWzxS1Y2wckblnXY57Z+sl6YdmLV+gxj2r8Qib7g4ZIk +lIlWR1OJNfw7kU4eryib4fc6nOh6O4AWZyYqAK6tqNQSS/eVG0LQTLTTEldHyVJL +dvBe+MsUQOj4nTndZW+QvFzbcm2D8lY5n2nBSxU5ypVoKZ1EqQzytFcLZpTN7d89 +EPj0qDyrV4NZlWAwL1AygCwnlwhMQjXEalVF1ylXwU3QzyZ/6MgvF6d3SSUlh+sq +XefuyigXw484cQQgbzopv6niMOmGP3of+yV4JQqUSb3IDmmT68XjGd2Dkxl4iPki +6ZwXf3CCi+c+i/zVEcufgZ3SLf8D99kUGE7v7fZ6AQKBgQD1ZX3RAla9hIhxCf+O +3D+I1j2LMrdjAh0ZKKqwMR4JnHX3mjQI6LwqIctPWTU8wYFECSh9klEclSdCa64s +uI/GNpcqPXejd0cAAdqHEEeG5sHMDt0oFSurL4lyud0GtZvwlzLuwEweuDtvT9cJ +Wfvl86uyO36IW8JdvUprYDctrQKBgQDycZ697qutBieZlGkHpnYWUAeImVA878sJ +w44NuXHvMxBPz+lbJGAg8Cn8fcxNAPqHIraK+kx3po8cZGQywKHUWsxi23ozHoxo ++bGqeQb9U661TnfdDspIXia+xilZt3mm5BPzOUuRqlh4Y9SOBpSWRmEhyw76w4ZP +OPxjWYAgwQKBgA/FehSYxeJgRjSdo+MWnK66tjHgDJE8bYpUZsP0JC4R9DL5oiaA +brd2fI6Y+SbyeNBallObt8LSgzdtnEAbjIH8uDJqyOmknNePRvAvR6mP4xyuR+Bv +m+Lgp0DMWTw5J9CKpydZDItc49T/mJ5tPhdFVd+am0NAQnmr1MCZ6nHxAoGABS3Y +LkaC9FdFUUqSU8+Chkd/YbOkuyiENdkvl6t2e52jo5DVc1T7mLiIrRQi4SI8N9bN +/3oJWCT+uaSLX2ouCtNFunblzWHBrhxnZzTeqVq4SLc8aESAnbslKL4i8/+vYZlN +s8xtiNcSvL+lMsOBORSXzpj/4Ot8WwTkn1qyGgECgYBKNTypzAHeLE6yVadFp3nQ +Ckq9yzvP/ib05rvgbvrne00YeOxqJ9gtTrzgh7koqJyX1L4NwdkEza4ilDWpucn0 +xiUZS4SoaJq6ZvcBYS62Yr1t8n09iG47YL8ibgtmH3L+svaotvpVxVK+d7BLevA/ +ZboOWVe3icTy64BT3OQhmg== +-----END RSA PRIVATE KEY-----` + // HTTPMultiBin can be used as a local alternative of httpbin.org. It offers both http and https servers, as well as real domains type HTTPMultiBin struct { Mux *http.ServeMux @@ -395,6 +445,8 @@ func NewHTTPMultiBin(t testing.TB) *HTTPMultiBin { "HTTP2BIN_PORT", http2URL.Port(), "GRPCBIN_ADDR", fmt.Sprintf("%s:%s", httpsDomain, http2URL.Port()), + "LOCALHOST_CERT", strings.ReplaceAll(localhostCert, "\n", "\\n"), + "LOCALHOST_KEY", strings.ReplaceAll(localhostKey, "\n", "\\n"), ), TLSClientConfig: tlsConfig, Dialer: dialer, From 9a2f3ba0638a1932f1f4bd7db9643e98121a4faf Mon Sep 17 00:00:00 2001 From: Chris Moran Date: Tue, 4 Jul 2023 12:42:42 -0400 Subject: [PATCH 06/24] Added tests for grpc.Client connectParams tls option --- js/modules/k6/grpc/client.go | 15 ++++++++------- js/modules/k6/grpc/client_test.go | 25 +++++++++++++++++++++++++ 2 files changed, 33 insertions(+), 7 deletions(-) diff --git a/js/modules/k6/grpc/client.go b/js/modules/k6/grpc/client.go index d0af1431a14..a3a3a77035d 100644 --- a/js/modules/k6/grpc/client.go +++ b/js/modules/k6/grpc/client.go @@ -140,14 +140,15 @@ func buildTLSConfigFromMap(tlsConfigMap map[string]interface{}) (*tls.Config, er if keystr, ok := tlsConfigMap["key"].(string); ok { key = []byte(keystr) } - cas := tlsConfigMap["cacerts"] - ca = make([][]byte, 0) - if casarr, ok := cas.([]string); ok { - for _, casEntry := range casarr { - ca = append(ca, []byte(casEntry)) + if cas, ok := tlsConfigMap["cacerts"]; ok { + ca = make([][]byte, 0) + if casarr, ok := cas.([]string); ok { + for _, casEntry := range casarr { + ca = append(ca, []byte(casEntry)) + } + } else if casstr, casstrok := cas.(string); casstrok { + ca = append(ca, []byte(casstr)) } - } else if casstr, casstrok := cas.(string); casstrok { - ca = append(ca, []byte(casstr)) } return buildTLSConfig(cert, key, ca) } diff --git a/js/modules/k6/grpc/client_test.go b/js/modules/k6/grpc/client_test.go index aa8ea3b3f96..1d411e9ca4e 100644 --- a/js/modules/k6/grpc/client_test.go +++ b/js/modules/k6/grpc/client_test.go @@ -5,6 +5,7 @@ import ( "context" "crypto/tls" "errors" + "fmt" "io" "net/url" "os" @@ -308,6 +309,30 @@ func TestClient(t *testing.T) { client.load([], "../../../../lib/testutils/httpmultibin/grpc_testing/test.proto");`}, vuString: codeBlock{code: `client.connect("GRPCBIN_ADDR", { tls: { cacerts: "LOCALHOST_CERT", cert: "LOCALHOST_CERT", key: "LOCALHOST_KEY" }});`}, }, + { + name: "ConnectTlsUnknownAuthority", + initString: codeBlock{code: ` + var client = new grpc.Client(); + client.load([], "../../../../lib/testutils/httpmultibin/grpc_testing/test.proto");`}, + vuString: codeBlock{code: fmt.Sprintf(`client.connect("GRPCBIN_ADDR", { timeout: '1s', tls: { cert: "%s", key: "%s" }});`, + "-----BEGIN CERTIFICATE-----\\n"+ + "MIIBVzCB/6ADAgECAgkAg/SeNG3XqB0wCgYIKoZIzj0EAwIwEDEOMAwGA1UEAwwF\\n"+ + "TXkgQ0EwIBcNMjIwMTIxMTUxMjM0WhgPMzAyMTA1MjQxNTEyMzRaMBExDzANBgNV\\n"+ + "BAMMBmNsaWVudDBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABKM7OJQMYG4KLtDA\\n"+ + "gZ8zOg2PimHMmQnjD2HtI4cSwIUJJnvHWLowbFe9fk6XeP9b3dK1ImUI++/EZdVr\\n"+ + "ABAcngejPzA9MA4GA1UdDwEB/wQEAwIBBjAMBgNVHRMBAf8EAjAAMB0GA1UdDgQW\\n"+ + "BBSttJe1mcPEnBOZ6wvKPG4zL0m1CzAKBggqhkjOPQQDAgNHADBEAiBPSLgKA/r9\\n"+ + "u/FW6W+oy6Odm1kdNMGCI472iTn545GwJgIgb3UQPOUTOj0IN4JLJYfmYyXviqsy\\n"+ + "zk9eWNHFXDA9U6U=\\n"+ + "-----END CERTIFICATE-----", + "-----BEGIN EC PRIVATE KEY-----\\n"+ + "MHcCAQEEINDaMGkOT3thu1A0LfLJr3Jd011/aEG6OArmEQaujwgpoAoGCCqGSM49\\n"+ + "AwEHoUQDQgAEozs4lAxgbgou0MCBnzM6DY+KYcyZCeMPYe0jhxLAhQkme8dYujBs\\n"+ + "V71+Tpd4/1vd0rUiZQj778Rl1WsAEByeBw==\\n"+ + "-----END EC PRIVATE KEY-----"), + err: "certificate signed by unknown authority", + }, + }, { name: "Connect", initString: codeBlock{code: ` From 6850c71ee97b397d9d06a75705afa70347cea1a6 Mon Sep 17 00:00:00 2001 From: Chris Moran Date: Tue, 4 Jul 2023 13:24:36 -0400 Subject: [PATCH 07/24] Added missed password field for encrypted certificate key on tls option Added tests around tls password and encrypted certificate key --- js/modules/k6/grpc/client.go | 47 ++++++++++++- js/modules/k6/grpc/client_test.go | 78 +++++++++++++++++----- lib/testutils/httpmultibin/httpmultibin.go | 34 ++++++++++ 3 files changed, 141 insertions(+), 18 deletions(-) diff --git a/js/modules/k6/grpc/client.go b/js/modules/k6/grpc/client.go index a3a3a77035d..51de86d102d 100644 --- a/js/modules/k6/grpc/client.go +++ b/js/modules/k6/grpc/client.go @@ -4,6 +4,7 @@ import ( "context" "crypto/tls" "crypto/x509" + "encoding/pem" "errors" "fmt" "io" @@ -110,6 +111,32 @@ func (c *Client) LoadProtoset(protosetPath string) ([]MethodInfo, error) { return c.convertToMethodInfo(fdset) } +func decryptPrivateKey(key, password []byte) ([]byte, error) { + block, _ := pem.Decode(key) + if block == nil { + return nil, fmt.Errorf("failed to decode PEM key") + } + + blockType := block.Type + if blockType == "ENCRYPTED PRIVATE KEY" { + return nil, fmt.Errorf("encrypted pkcs8 formatted key is not supported") + } + /* + Even though `DecryptPEMBlock` has been deprecated since 1.16.x it is still + being used here because it is deprecated due to it not supporting *good* crypography + ultimately though we want to support something so we will be using it for now. + */ + decryptedKey, err := x509.DecryptPEMBlock(block, password) //nolint:staticcheck + if err != nil { + return nil, err + } + key = pem.EncodeToMemory(&pem.Block{ + Type: blockType, + Bytes: decryptedKey, + }) + return key, nil +} + func buildTLSConfig(certificate, key []byte, caCertificates [][]byte) (*tls.Config, error) { var cp *x509.CertPool if len(caCertificates) > 0 { @@ -132,14 +159,23 @@ func buildTLSConfig(certificate, key []byte, caCertificates [][]byte) (*tls.Conf } func buildTLSConfigFromMap(tlsConfigMap map[string]interface{}) (*tls.Config, error) { - var cert, key []byte + var cert, key, pass []byte var ca [][]byte + var err error if certstr, ok := tlsConfigMap["cert"].(string); ok { cert = []byte(certstr) } if keystr, ok := tlsConfigMap["key"].(string); ok { key = []byte(keystr) } + if passstr, ok := tlsConfigMap["password"].(string); ok { + pass = []byte(passstr) + if len(pass) > 0 { + if key, err = decryptPrivateKey(key, pass); err != nil { + return nil, err + } + } + } if cas, ok := tlsConfigMap["cacerts"]; ok { ca = make([][]byte, 0) if casarr, ok := cas.([]string); ok { @@ -477,7 +513,7 @@ func (c *Client) parseConnectParams(raw map[string]interface{}) (connectParams, case "tls": var ok bool params.TLS, ok = v.(map[string]interface{}) - err := fmt.Errorf("invalid tls value: '%#v', tls needs cert, key, and (optionally) cacerts", v) + err := fmt.Errorf("invalid tls value: '%#v', tls needs cert, key, (optional) password, and (optionally) cacerts", v) if !ok { return params, err @@ -496,6 +532,13 @@ func (c *Client) parseConnectParams(raw map[string]interface{}) (connectParams, } else { return params, err } + + // optional map keys below + if pass, passok := params.TLS["password"]; passok { + if _, ok = pass.(string); !ok { + return params, fmt.Errorf("invalid tls password value: '%#v', it needs to be a string", v) + } + } if cacerts, cacertsok := params.TLS["cacerts"]; cacertsok { if _, ok = cacerts.([]interface{}); !ok { if _, ok = cacerts.(string); !ok { diff --git a/js/modules/k6/grpc/client_test.go b/js/modules/k6/grpc/client_test.go index 1d411e9ca4e..9f2c4cb1b34 100644 --- a/js/modules/k6/grpc/client_test.go +++ b/js/modules/k6/grpc/client_test.go @@ -292,6 +292,16 @@ func TestClient(t *testing.T) { err: `invalid grpc.connect() parameters: invalid tls key value: 'map[string]interface {}{"cert":"", "key":0}', it needs to be a PEM formatted string`, }, }, + { + name: "ConnectTlsInvalidTlsParamPasswordType", + initString: codeBlock{code: ` + var client = new grpc.Client(); + client.load([], "../../../../lib/testutils/httpmultibin/grpc_testing/test.proto");`}, + vuString: codeBlock{ + code: `client.connect("GRPCBIN_ADDR", { tls: { cert: "", key: "", password: 0 }});`, + err: `invalid grpc.connect() parameters: invalid tls password value: 'map[string]interface {}{"cert":"", "key":"", "password":0}', it needs to be a string`, + }, + }, { name: "ConnectTlsInvalidTlsParamCACertsType", initString: codeBlock{code: ` @@ -309,30 +319,66 @@ func TestClient(t *testing.T) { client.load([], "../../../../lib/testutils/httpmultibin/grpc_testing/test.proto");`}, vuString: codeBlock{code: `client.connect("GRPCBIN_ADDR", { tls: { cacerts: "LOCALHOST_CERT", cert: "LOCALHOST_CERT", key: "LOCALHOST_KEY" }});`}, }, + { + name: "ConnectTlsEncryptedKey", + initString: codeBlock{code: ` + var client = new grpc.Client(); + client.load([], "../../../../lib/testutils/httpmultibin/grpc_testing/test.proto");`}, + vuString: codeBlock{code: `client.connect("GRPCBIN_ADDR", { tls: { cacerts: "LOCALHOST_CERT", cert: "LOCALHOST_CERT", key: "LOCALHOST_ENCRYPTED_KEY", password:"abc123" }});`}, + }, { name: "ConnectTlsUnknownAuthority", initString: codeBlock{code: ` var client = new grpc.Client(); client.load([], "../../../../lib/testutils/httpmultibin/grpc_testing/test.proto");`}, - vuString: codeBlock{code: fmt.Sprintf(`client.connect("GRPCBIN_ADDR", { timeout: '1s', tls: { cert: "%s", key: "%s" }});`, - "-----BEGIN CERTIFICATE-----\\n"+ - "MIIBVzCB/6ADAgECAgkAg/SeNG3XqB0wCgYIKoZIzj0EAwIwEDEOMAwGA1UEAwwF\\n"+ - "TXkgQ0EwIBcNMjIwMTIxMTUxMjM0WhgPMzAyMTA1MjQxNTEyMzRaMBExDzANBgNV\\n"+ - "BAMMBmNsaWVudDBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABKM7OJQMYG4KLtDA\\n"+ - "gZ8zOg2PimHMmQnjD2HtI4cSwIUJJnvHWLowbFe9fk6XeP9b3dK1ImUI++/EZdVr\\n"+ - "ABAcngejPzA9MA4GA1UdDwEB/wQEAwIBBjAMBgNVHRMBAf8EAjAAMB0GA1UdDgQW\\n"+ - "BBSttJe1mcPEnBOZ6wvKPG4zL0m1CzAKBggqhkjOPQQDAgNHADBEAiBPSLgKA/r9\\n"+ - "u/FW6W+oy6Odm1kdNMGCI472iTn545GwJgIgb3UQPOUTOj0IN4JLJYfmYyXviqsy\\n"+ - "zk9eWNHFXDA9U6U=\\n"+ - "-----END CERTIFICATE-----", - "-----BEGIN EC PRIVATE KEY-----\\n"+ - "MHcCAQEEINDaMGkOT3thu1A0LfLJr3Jd011/aEG6OArmEQaujwgpoAoGCCqGSM49\\n"+ - "AwEHoUQDQgAEozs4lAxgbgou0MCBnzM6DY+KYcyZCeMPYe0jhxLAhQkme8dYujBs\\n"+ - "V71+Tpd4/1vd0rUiZQj778Rl1WsAEByeBw==\\n"+ - "-----END EC PRIVATE KEY-----"), + vuString: codeBlock{ + code: fmt.Sprintf(`client.connect("GRPCBIN_ADDR", { timeout: '1s', tls: { cert: "%s", key: "%s" }});`, + "-----BEGIN CERTIFICATE-----\\n"+ + "MIIBVzCB/6ADAgECAgkAg/SeNG3XqB0wCgYIKoZIzj0EAwIwEDEOMAwGA1UEAwwF\\n"+ + "TXkgQ0EwIBcNMjIwMTIxMTUxMjM0WhgPMzAyMTA1MjQxNTEyMzRaMBExDzANBgNV\\n"+ + "BAMMBmNsaWVudDBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABKM7OJQMYG4KLtDA\\n"+ + "gZ8zOg2PimHMmQnjD2HtI4cSwIUJJnvHWLowbFe9fk6XeP9b3dK1ImUI++/EZdVr\\n"+ + "ABAcngejPzA9MA4GA1UdDwEB/wQEAwIBBjAMBgNVHRMBAf8EAjAAMB0GA1UdDgQW\\n"+ + "BBSttJe1mcPEnBOZ6wvKPG4zL0m1CzAKBggqhkjOPQQDAgNHADBEAiBPSLgKA/r9\\n"+ + "u/FW6W+oy6Odm1kdNMGCI472iTn545GwJgIgb3UQPOUTOj0IN4JLJYfmYyXviqsy\\n"+ + "zk9eWNHFXDA9U6U=\\n"+ + "-----END CERTIFICATE-----", + "-----BEGIN EC PRIVATE KEY-----\\n"+ + "MHcCAQEEINDaMGkOT3thu1A0LfLJr3Jd011/aEG6OArmEQaujwgpoAoGCCqGSM49\\n"+ + "AwEHoUQDQgAEozs4lAxgbgou0MCBnzM6DY+KYcyZCeMPYe0jhxLAhQkme8dYujBs\\n"+ + "V71+Tpd4/1vd0rUiZQj778Rl1WsAEByeBw==\\n"+ + "-----END EC PRIVATE KEY-----"), err: "certificate signed by unknown authority", }, }, + { + name: "ConnectTlsEncryptedUnknownAuthority", + initString: codeBlock{code: ` + var client = new grpc.Client(); + client.load([], "../../../../lib/testutils/httpmultibin/grpc_testing/test.proto");`}, + vuString: codeBlock{ + code: fmt.Sprintf(`client.connect("GRPCBIN_ADDR", { timeout: '1s', tls: { cert: "%s", key: "%s", password: "abc321" }});`, + "-----BEGIN CERTIFICATE-----\\n"+ + "MIIBVzCB/6ADAgECAgkAg/SeNG3XqB0wCgYIKoZIzj0EAwIwEDEOMAwGA1UEAwwF\\n"+ + "TXkgQ0EwIBcNMjIwMTIxMTUxMjM0WhgPMzAyMTA1MjQxNTEyMzRaMBExDzANBgNV\\n"+ + "BAMMBmNsaWVudDBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABKM7OJQMYG4KLtDA\\n"+ + "gZ8zOg2PimHMmQnjD2HtI4cSwIUJJnvHWLowbFe9fk6XeP9b3dK1ImUI++/EZdVr\\n"+ + "ABAcngejPzA9MA4GA1UdDwEB/wQEAwIBBjAMBgNVHRMBAf8EAjAAMB0GA1UdDgQW\\n"+ + "BBSttJe1mcPEnBOZ6wvKPG4zL0m1CzAKBggqhkjOPQQDAgNHADBEAiBPSLgKA/r9\\n"+ + "u/FW6W+oy6Odm1kdNMGCI472iTn545GwJgIgb3UQPOUTOj0IN4JLJYfmYyXviqsy\\n"+ + "zk9eWNHFXDA9U6U=\\n"+ + "-----END CERTIFICATE-----", + "-----BEGIN EC PRIVATE KEY-----\\n"+ + "Proc-Type: 4,ENCRYPTED\\n"+ + "DEK-Info: AES-256-CBC,3E311E9B602231BFB5C752071EE7D652"+ + "\\n\\n"+ + "sAKeqbacug0v4ruE1A0CACwGVEGBQVOl1CiGVp5RsxgNZKXzMS6EsTTNLw378coF\\n"+ + "KXbF+he05HIuzToOz2ANLXov1iCrVpotKVB4l2obTQvg+5VET902ky99Mc9Us7jd\\n"+ + "UwW8LpXlSlhcNWuUfK6wyosL42TbcIxjqZWaESW+6ww=\\n"+ + "-----END EC PRIVATE KEY-----"), + err: "x509: decryption password incorrect", + }, + }, { name: "Connect", initString: codeBlock{code: ` diff --git a/lib/testutils/httpmultibin/httpmultibin.go b/lib/testutils/httpmultibin/httpmultibin.go index 81e3058a854..b0fbfad3b70 100644 --- a/lib/testutils/httpmultibin/httpmultibin.go +++ b/lib/testutils/httpmultibin/httpmultibin.go @@ -117,6 +117,39 @@ xiUZS4SoaJq6ZvcBYS62Yr1t8n09iG47YL8ibgtmH3L+svaotvpVxVK+d7BLevA/ ZboOWVe3icTy64BT3OQhmg== -----END RSA PRIVATE KEY-----` +// trivial password: abc123 +const localhostEncryptedKey = `-----BEGIN RSA PRIVATE KEY----- +Proc-Type: 4,ENCRYPTED +DEK-Info: AES-256-CBC,B2557B8662FBEC979823E6F51B8ED777 + +9xXt7ZCYHjYz501uiQKPLpxmz1qGNwu/u2VCwu/dFql2BLGfKrk5j4ZvaoKqUwVB +QfUaisSv1g++Rh13qDOOvRO38TF7aQPxImqCw2ew/fFC0JTiPWpSaQtIcWOxASpa +s84Z4LIolfeLxXyOG3JwWeKG/WQCMf1QNM+LXlfCJU6vdY0KeoDUcp4CkFNDdUrx +qGaF4xJxaQfLSSLuXlWTYmz+IlPMy/xOUCn3eSemWd4ZBdFUIwsSsuQkZHzthjJg +DbvAzuEzvQENXInnfYHkA4XHM+SMV+4d+aZgPNUSWv3YfXeOhapdpeAjVq6Q2TiX +xkFOjFYUKWO6sLWS5WfB1eqwwh9vNkZVHbmYYUvJbc2Aw0EJlQWxeQ6Vj0d6TIev +EPr6jFROaJ5kTd2XxG4HzJcGWsV27q4r159GGGmrZk1GjGZKImWP6Y5f3t4u+uJZ +EHVGx+SkilEaOM0ar1sXGgtIif741GRYYibd9hD1+0hSOxyVpehtEeb/wJRG4Vd6 +i07ANkqwOop4K/nW5OOXKEz6eDrXAAJ2gzjN74WCyR5nJ2XoTjUa9Vo9hNrBcmtL +dRoeHOu9BhN+mu8YPwdisjtK6AJorsf0bQWqGpnexFw9Fq/XMpCTBT6P5X6gsNAs +RKvS5bwai7e9pJqZY+iJjdCTnFflDTX/r/lt4SxIkoZvGoGEVeMJsc6yFZP6yDCx +zjZkk0R3WsVusheATVBJJJcsHLdaUR707TU5lmhVFx0BYcBVrfKNc1h0E+alSM5R +ij3T88ipk8BN4/e6gUpQCptMtda7wFCxiAIU+l1skGmM29ZvPB8BNAbMFCxioBGk +Me6QWcqOTLzoLwFHH2hSPYhZKeadCyz53OKbyIK/m06BXMVpFaIxesJZx6qW3aew +gShlUwr7yu9QJlxGZX0wIC1dyT69lRcLsbqzqnp6EMspSEZYvysq2k0GKZyAqLnr +e9CClm0wMnj45SK34/s1BWZNbBgXkDlzTKwMMN6RRY09seLoooJ64QPXgvW8T96P +0my7xtvtmt3h7krsudV68JbgaMotjvzV2vOxgD+s93QQIByIU+mTef5giEshERTL +8cOw4jp99p0wswH9hbn8TQsSf1UPFsL8P3HbD6HiNKKA1YyijgSIEQ6z0H0ALujb +hvweCPpwvOQAXxg+cpumn0bu7oLonWhdy+pkfYEvw/UWNX/7Qd7EIp8v84FI6J0U +jX2iIIBm8rbA+lF10jo7GobPoQ4bGDEQOsNxuUSYvc07HoMpEVTH9Kg8dOZmvRQp +pwyG4/o2+5LWXw8c1+1KNvdlhM8iMrCzz/0gok7UHLvisb3MruZE9c6Ujoua09Tu +shPGfJzXelJiRUwajFFAeBS/TPPBqi8KjFrz+sjYA8rFk7rHZZYW2p1n11Z+SLWj +MwBqQ5yCLohZe5UELdei8h0OuUOgfvnmJWcNk0vhlC1RxzjgUE1ZuQgD+yVbnWEu +XtcpRl5KCY7LnKflxpY5flhLdL0I4pH3coBcWn+87F8TCwxE6xt9Db/ny0Upoupf +iZ1HoCyF0iJj75Duu9Ssr61gR8Gd/R6agXEhi19o517yeK7x+a+UPAinojjROQGD +-----END RSA PRIVATE KEY----- +` + // HTTPMultiBin can be used as a local alternative of httpbin.org. It offers both http and https servers, as well as real domains type HTTPMultiBin struct { Mux *http.ServeMux @@ -447,6 +480,7 @@ func NewHTTPMultiBin(t testing.TB) *HTTPMultiBin { "GRPCBIN_ADDR", fmt.Sprintf("%s:%s", httpsDomain, http2URL.Port()), "LOCALHOST_CERT", strings.ReplaceAll(localhostCert, "\n", "\\n"), "LOCALHOST_KEY", strings.ReplaceAll(localhostKey, "\n", "\\n"), + "LOCALHOST_ENCRYPTED_KEY", strings.ReplaceAll(localhostEncryptedKey, "\n", "\\n"), ), TLSClientConfig: tlsConfig, Dialer: dialer, From 4cbf4161189c3ad7b270d20a279d24ed51a14fb9 Mon Sep 17 00:00:00 2001 From: Chris Moran Date: Wed, 5 Jul 2023 16:19:07 -0400 Subject: [PATCH 08/24] Address security scan for "RSA Private Key exposed on GitHub" for localhostKey and localhostEncryptedKey. (Used the same approach `net/http/internal/testcert/testcert.go` uses) --- lib/testutils/httpmultibin/httpmultibin.go | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/lib/testutils/httpmultibin/httpmultibin.go b/lib/testutils/httpmultibin/httpmultibin.go index b0fbfad3b70..8c1015bb8f4 100644 --- a/lib/testutils/httpmultibin/httpmultibin.go +++ b/lib/testutils/httpmultibin/httpmultibin.go @@ -88,7 +88,7 @@ grw/ZQTTIVjjh4JBSW3WyWgNo/ikC1lrVxzl4iPUGptxT36Cr7Zk2Bsg0XqwbOvK WkBKOclmOV2xlTVuPw== -----END CERTIFICATE-----` -const localhostKey = `-----BEGIN RSA PRIVATE KEY----- +var localhostKey = testingKey(`-----BEGIN RSA TESTING KEY----- MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQDoZtrm0dXV0Aqi 4Bpc7f95sNRTiu/AJSD8I1onY9PnEsPg3VVxvytsVJbYdcqr4w99V3AgpH/UNzMS gAZ/8lZBNbsSDOVesJ3euVqMRfYPvd9pYl6QPRRpSDPm+2tNdn3QFAvta9EgJ3sW @@ -115,10 +115,10 @@ s8xtiNcSvL+lMsOBORSXzpj/4Ot8WwTkn1qyGgECgYBKNTypzAHeLE6yVadFp3nQ Ckq9yzvP/ib05rvgbvrne00YeOxqJ9gtTrzgh7koqJyX1L4NwdkEza4ilDWpucn0 xiUZS4SoaJq6ZvcBYS62Yr1t8n09iG47YL8ibgtmH3L+svaotvpVxVK+d7BLevA/ ZboOWVe3icTy64BT3OQhmg== ------END RSA PRIVATE KEY-----` +-----END RSA TESTING KEY-----`) // trivial password: abc123 -const localhostEncryptedKey = `-----BEGIN RSA PRIVATE KEY----- +var localhostEncryptedKey = testingKey(`-----BEGIN RSA TESTING KEY----- Proc-Type: 4,ENCRYPTED DEK-Info: AES-256-CBC,B2557B8662FBEC979823E6F51B8ED777 @@ -147,8 +147,11 @@ shPGfJzXelJiRUwajFFAeBS/TPPBqi8KjFrz+sjYA8rFk7rHZZYW2p1n11Z+SLWj MwBqQ5yCLohZe5UELdei8h0OuUOgfvnmJWcNk0vhlC1RxzjgUE1ZuQgD+yVbnWEu XtcpRl5KCY7LnKflxpY5flhLdL0I4pH3coBcWn+87F8TCwxE6xt9Db/ny0Upoupf iZ1HoCyF0iJj75Duu9Ssr61gR8Gd/R6agXEhi19o517yeK7x+a+UPAinojjROQGD ------END RSA PRIVATE KEY----- -` +-----END RSA TESTING KEY----- +`) + +// Because security scans about "leaked keys". The above keys are _not_ "leaked keys". +func testingKey(s string) string { return strings.ReplaceAll(s, "TESTING KEY", "PRIVATE KEY") } // HTTPMultiBin can be used as a local alternative of httpbin.org. It offers both http and https servers, as well as real domains type HTTPMultiBin struct { From 7bec30a43763eddb3e6b699c37a41c59d89270aa Mon Sep 17 00:00:00 2001 From: Chris Moran <116747924+chrismoran-mica@users.noreply.github.com> Date: Thu, 6 Jul 2023 07:12:11 -0400 Subject: [PATCH 09/24] Update js/modules/k6/grpc/client.go Nice catch. Thank you. Co-authored-by: Mihail Stoykov <312246+mstoykov@users.noreply.github.com> --- js/modules/k6/grpc/client.go | 1 - 1 file changed, 1 deletion(-) diff --git a/js/modules/k6/grpc/client.go b/js/modules/k6/grpc/client.go index 51de86d102d..843c4b98c77 100644 --- a/js/modules/k6/grpc/client.go +++ b/js/modules/k6/grpc/client.go @@ -470,7 +470,6 @@ func (c *Client) parseConnectParams(raw map[string]interface{}) (connectParams, Timeout: time.Minute, MaxReceiveSize: 0, MaxSendSize: 0, - TLS: make(map[string]interface{}), } for k, v := range raw { switch k { From 6fe2efa6ae573bf4469b7318dcbc6e65506c89fc Mon Sep 17 00:00:00 2001 From: Chris Moran <116747924+chrismoran-mica@users.noreply.github.com> Date: Thu, 6 Jul 2023 07:13:09 -0400 Subject: [PATCH 10/24] Update js/modules/k6/grpc/client.go Nice catch. Thank you. Co-authored-by: Mihail Stoykov <312246+mstoykov@users.noreply.github.com> --- js/modules/k6/grpc/client.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/js/modules/k6/grpc/client.go b/js/modules/k6/grpc/client.go index 843c4b98c77..b41c9b6ffaf 100644 --- a/js/modules/k6/grpc/client.go +++ b/js/modules/k6/grpc/client.go @@ -147,9 +147,9 @@ func buildTLSConfig(certificate, key []byte, caCertificates [][]byte) (*tls.Conf } } } - cert, certerr := tls.X509KeyPair(certificate, key) - if certerr != nil { - return nil, fmt.Errorf("failed to append certificate from PEM") + cert, err := tls.X509KeyPair(certificate, key) + if err != nil { + return nil, fmt.Errorf("failed to append certificate from PEM: %w", err) } return &tls.Config{ RootCAs: cp, From 5d7cfd9e9f3c57345a90e3b18bce2594aa714a12 Mon Sep 17 00:00:00 2001 From: Chris Moran Date: Thu, 6 Jul 2023 07:13:49 -0400 Subject: [PATCH 11/24] PR Feedback --- js/modules/k6/grpc/client.go | 1 - 1 file changed, 1 deletion(-) diff --git a/js/modules/k6/grpc/client.go b/js/modules/k6/grpc/client.go index b41c9b6ffaf..c3693dbb929 100644 --- a/js/modules/k6/grpc/client.go +++ b/js/modules/k6/grpc/client.go @@ -154,7 +154,6 @@ func buildTLSConfig(certificate, key []byte, caCertificates [][]byte) (*tls.Conf return &tls.Config{ RootCAs: cp, Certificates: []tls.Certificate{cert}, - MinVersion: tls.VersionTLS12, }, nil } From fd512e6de4bb996740f9fa62fea2f6190348710e Mon Sep 17 00:00:00 2001 From: Chris Moran Date: Fri, 7 Jul 2023 08:19:41 -0400 Subject: [PATCH 12/24] Cleaned (removed) prior changes to httpmultibin.go Added positive and negative test cases for grpc client connect with tls parameter --- js/modules/k6/grpc/client.go | 80 +++-- js/modules/k6/grpc/client_test.go | 363 ++++++++++++++------- lib/testutils/httpmultibin/httpmultibin.go | 89 ----- 3 files changed, 294 insertions(+), 238 deletions(-) diff --git a/js/modules/k6/grpc/client.go b/js/modules/k6/grpc/client.go index c3693dbb929..d9e8f3f5e18 100644 --- a/js/modules/k6/grpc/client.go +++ b/js/modules/k6/grpc/client.go @@ -111,6 +111,7 @@ func (c *Client) LoadProtoset(protosetPath string) ([]MethodInfo, error) { return c.convertToMethodInfo(fdset) } +// Note: this function was lifted from `lib/options.go` func decryptPrivateKey(key, password []byte) ([]byte, error) { block, _ := pem.Decode(key) if block == nil { @@ -137,7 +138,7 @@ func decryptPrivateKey(key, password []byte) ([]byte, error) { return key, nil } -func buildTLSConfig(certificate, key []byte, caCertificates [][]byte) (*tls.Config, error) { +func buildTLSConfig(parentConfig *tls.Config, certificate, key []byte, caCertificates [][]byte) (*tls.Config, error) { var cp *x509.CertPool if len(caCertificates) > 0 { cp, _ = x509.SystemCertPool() @@ -147,17 +148,30 @@ func buildTLSConfig(certificate, key []byte, caCertificates [][]byte) (*tls.Conf } } } - cert, err := tls.X509KeyPair(certificate, key) - if err != nil { - return nil, fmt.Errorf("failed to append certificate from PEM: %w", err) + + // Ignoring 'TLS MinVersion is too low' because this tls.Config will inherit MinValue and MaxValue + // from the vu state tls.Config + + //nolint:golint,gosec + tlsCfg := &tls.Config{ + CipherSuites: parentConfig.CipherSuites, + InsecureSkipVerify: parentConfig.InsecureSkipVerify, + MinVersion: parentConfig.MinVersion, + MaxVersion: parentConfig.MaxVersion, + Renegotiation: parentConfig.Renegotiation, + RootCAs: cp, + } + if len(certificate) > 0 && len(key) > 0 { + cert, err := tls.X509KeyPair(certificate, key) + if err != nil { + return nil, fmt.Errorf("failed to append certificate from PEM: %w", err) + } + tlsCfg.Certificates = []tls.Certificate{cert} } - return &tls.Config{ - RootCAs: cp, - Certificates: []tls.Certificate{cert}, - }, nil + return tlsCfg, nil } -func buildTLSConfigFromMap(tlsConfigMap map[string]interface{}) (*tls.Config, error) { +func buildTLSConfigFromMap(parentConfig *tls.Config, tlsConfigMap map[string]interface{}) (*tls.Config, error) { var cert, key, pass []byte var ca [][]byte var err error @@ -167,8 +181,8 @@ func buildTLSConfigFromMap(tlsConfigMap map[string]interface{}) (*tls.Config, er if keystr, ok := tlsConfigMap["key"].(string); ok { key = []byte(keystr) } - if passstr, ok := tlsConfigMap["password"].(string); ok { - pass = []byte(passstr) + if passwordStr, ok := tlsConfigMap["password"].(string); ok { + pass = []byte(passwordStr) if len(pass) > 0 { if key, err = decryptPrivateKey(key, pass); err != nil { return nil, err @@ -176,16 +190,20 @@ func buildTLSConfigFromMap(tlsConfigMap map[string]interface{}) (*tls.Config, er } } if cas, ok := tlsConfigMap["cacerts"]; ok { - ca = make([][]byte, 0) - if casarr, ok := cas.([]string); ok { - for _, casEntry := range casarr { - ca = append(ca, []byte(casEntry)) + var caCertsArray []interface{} + if caCertsArray, ok = cas.([]interface{}); ok { + ca = make([][]byte, len(caCertsArray)) + for i, entry := range caCertsArray { + var entryStr string + if entryStr, ok = entry.(string); ok { + ca[i] = []byte(entryStr) + } } - } else if casstr, casstrok := cas.(string); casstrok { - ca = append(ca, []byte(casstr)) + } else if caCertStr, caCertStrOk := cas.(string); caCertStrOk { + ca = [][]byte{[]byte(caCertStr)} } } - return buildTLSConfig(cert, key, ca) + return buildTLSConfig(parentConfig, cert, key, ca) } // Connect is a block dial to the gRPC server at the given address (host:port) @@ -206,7 +224,7 @@ func (c *Client) Connect(addr string, params map[string]interface{}) (bool, erro if !p.IsPlaintext { var tlsCfg *tls.Config if len(p.TLS) > 0 { - if tlsCfg, err = buildTLSConfigFromMap(p.TLS); err != nil { + if tlsCfg, err = buildTLSConfigFromMap(state.TLSConfig.Clone(), p.TLS); err != nil { return false, err } } else { @@ -511,38 +529,38 @@ func (c *Client) parseConnectParams(raw map[string]interface{}) (connectParams, case "tls": var ok bool params.TLS, ok = v.(map[string]interface{}) - err := fmt.Errorf("invalid tls value: '%#v', tls needs cert, key, (optional) password, and (optionally) cacerts", v) if !ok { - return params, err + return params, fmt.Errorf("invalid tls value: '%#v', expected (optional) keys: cert, key, password, and cacerts", v) } + // optional map keys below if cert, certok := params.TLS["cert"]; certok { if _, ok = cert.(string); !ok { return params, fmt.Errorf("invalid tls cert value: '%#v', it needs to be a PEM formatted string", v) } - } else { - return params, err } if key, keyok := params.TLS["key"]; keyok { if _, ok = key.(string); !ok { return params, fmt.Errorf("invalid tls key value: '%#v', it needs to be a PEM formatted string", v) } - } else { - return params, err } - - // optional map keys below if pass, passok := params.TLS["password"]; passok { if _, ok = pass.(string); !ok { return params, fmt.Errorf("invalid tls password value: '%#v', it needs to be a string", v) } } if cacerts, cacertsok := params.TLS["cacerts"]; cacertsok { - if _, ok = cacerts.([]interface{}); !ok { - if _, ok = cacerts.(string); !ok { - return params, fmt.Errorf("invalid tls cacerts value: '%#v',"+ - " it needs to be a string or string[] of PEM formatted strings", v) + var cacertsArray []interface{} + if cacertsArray, ok = cacerts.([]interface{}); ok { + for _, cacertsArrayEntry := range cacertsArray { + if _, ok = cacertsArrayEntry.(string); !ok { + return params, fmt.Errorf("invalid tls cacerts value: '%#v',"+ + " it needs to be a string or string[] of PEM formatted strings", v) + } } + } else if _, ok = cacerts.(string); !ok { + return params, fmt.Errorf("invalid tls cacerts value: '%#v',"+ + " it needs to be a string or string[] of PEM formatted strings", v) } } default: diff --git a/js/modules/k6/grpc/client_test.go b/js/modules/k6/grpc/client_test.go index 9f2c4cb1b34..2e001b89202 100644 --- a/js/modules/k6/grpc/client_test.go +++ b/js/modules/k6/grpc/client_test.go @@ -4,6 +4,7 @@ import ( "bytes" "context" "crypto/tls" + "crypto/x509" "errors" "fmt" "io" @@ -71,7 +72,7 @@ func TestClient(t *testing.T) { samples := make(chan metrics.SampleContainer, 1000) testRuntime := modulestest.NewRuntime(t) - cwd, err := os.Getwd() + cwd, err := os.Getwd() //nolint:golint,forbidigo require.NoError(t, err) fs := fsext.NewOsFs() if isWindows { @@ -262,123 +263,6 @@ func TestClient(t *testing.T) { client.load([], "../../../../lib/testutils/httpmultibin/grpc_testing/test.proto");`}, vuString: codeBlock{code: `client.connect("GRPCBIN_ADDR", { timeout: 3456.3 });`}, }, - { - name: "ConnectTlsInvalidEmptyTls", - initString: codeBlock{code: ` - var client = new grpc.Client(); - client.load([], "../../../../lib/testutils/httpmultibin/grpc_testing/test.proto");`}, - vuString: codeBlock{ - code: `client.connect("GRPCBIN_ADDR", { tls: { }});`, - err: "invalid grpc.connect() parameters: invalid tls value: 'map[string]interface {}{}', tls needs cert, key, and (optionally) cacerts", - }, - }, - { - name: "ConnectTlsInvalidTlsParamCertType", - initString: codeBlock{code: ` - var client = new grpc.Client(); - client.load([], "../../../../lib/testutils/httpmultibin/grpc_testing/test.proto");`}, - vuString: codeBlock{ - code: `client.connect("GRPCBIN_ADDR", { tls: { cert: 0 }});`, - err: `invalid grpc.connect() parameters: invalid tls cert value: 'map[string]interface {}{"cert":0}', it needs to be a PEM formatted string`, - }, - }, - { - name: "ConnectTlsInvalidTlsParamKeyType", - initString: codeBlock{code: ` - var client = new grpc.Client(); - client.load([], "../../../../lib/testutils/httpmultibin/grpc_testing/test.proto");`}, - vuString: codeBlock{ - code: `client.connect("GRPCBIN_ADDR", { tls: { cert: "", key: 0 }});`, - err: `invalid grpc.connect() parameters: invalid tls key value: 'map[string]interface {}{"cert":"", "key":0}', it needs to be a PEM formatted string`, - }, - }, - { - name: "ConnectTlsInvalidTlsParamPasswordType", - initString: codeBlock{code: ` - var client = new grpc.Client(); - client.load([], "../../../../lib/testutils/httpmultibin/grpc_testing/test.proto");`}, - vuString: codeBlock{ - code: `client.connect("GRPCBIN_ADDR", { tls: { cert: "", key: "", password: 0 }});`, - err: `invalid grpc.connect() parameters: invalid tls password value: 'map[string]interface {}{"cert":"", "key":"", "password":0}', it needs to be a string`, - }, - }, - { - name: "ConnectTlsInvalidTlsParamCACertsType", - initString: codeBlock{code: ` - var client = new grpc.Client(); - client.load([], "../../../../lib/testutils/httpmultibin/grpc_testing/test.proto");`}, - vuString: codeBlock{ - code: `client.connect("GRPCBIN_ADDR", { tls: { cert: "", key: "", cacerts: 0 }});`, - err: `invalid grpc.connect() parameters: invalid tls cacerts value: 'map[string]interface {}{"cacerts":0, "cert":"", "key":""}', it needs to be a string or string[] of PEM formatted strings`, - }, - }, - { - name: "ConnectTls", - initString: codeBlock{code: ` - var client = new grpc.Client(); - client.load([], "../../../../lib/testutils/httpmultibin/grpc_testing/test.proto");`}, - vuString: codeBlock{code: `client.connect("GRPCBIN_ADDR", { tls: { cacerts: "LOCALHOST_CERT", cert: "LOCALHOST_CERT", key: "LOCALHOST_KEY" }});`}, - }, - { - name: "ConnectTlsEncryptedKey", - initString: codeBlock{code: ` - var client = new grpc.Client(); - client.load([], "../../../../lib/testutils/httpmultibin/grpc_testing/test.proto");`}, - vuString: codeBlock{code: `client.connect("GRPCBIN_ADDR", { tls: { cacerts: "LOCALHOST_CERT", cert: "LOCALHOST_CERT", key: "LOCALHOST_ENCRYPTED_KEY", password:"abc123" }});`}, - }, - { - name: "ConnectTlsUnknownAuthority", - initString: codeBlock{code: ` - var client = new grpc.Client(); - client.load([], "../../../../lib/testutils/httpmultibin/grpc_testing/test.proto");`}, - vuString: codeBlock{ - code: fmt.Sprintf(`client.connect("GRPCBIN_ADDR", { timeout: '1s', tls: { cert: "%s", key: "%s" }});`, - "-----BEGIN CERTIFICATE-----\\n"+ - "MIIBVzCB/6ADAgECAgkAg/SeNG3XqB0wCgYIKoZIzj0EAwIwEDEOMAwGA1UEAwwF\\n"+ - "TXkgQ0EwIBcNMjIwMTIxMTUxMjM0WhgPMzAyMTA1MjQxNTEyMzRaMBExDzANBgNV\\n"+ - "BAMMBmNsaWVudDBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABKM7OJQMYG4KLtDA\\n"+ - "gZ8zOg2PimHMmQnjD2HtI4cSwIUJJnvHWLowbFe9fk6XeP9b3dK1ImUI++/EZdVr\\n"+ - "ABAcngejPzA9MA4GA1UdDwEB/wQEAwIBBjAMBgNVHRMBAf8EAjAAMB0GA1UdDgQW\\n"+ - "BBSttJe1mcPEnBOZ6wvKPG4zL0m1CzAKBggqhkjOPQQDAgNHADBEAiBPSLgKA/r9\\n"+ - "u/FW6W+oy6Odm1kdNMGCI472iTn545GwJgIgb3UQPOUTOj0IN4JLJYfmYyXviqsy\\n"+ - "zk9eWNHFXDA9U6U=\\n"+ - "-----END CERTIFICATE-----", - "-----BEGIN EC PRIVATE KEY-----\\n"+ - "MHcCAQEEINDaMGkOT3thu1A0LfLJr3Jd011/aEG6OArmEQaujwgpoAoGCCqGSM49\\n"+ - "AwEHoUQDQgAEozs4lAxgbgou0MCBnzM6DY+KYcyZCeMPYe0jhxLAhQkme8dYujBs\\n"+ - "V71+Tpd4/1vd0rUiZQj778Rl1WsAEByeBw==\\n"+ - "-----END EC PRIVATE KEY-----"), - err: "certificate signed by unknown authority", - }, - }, - { - name: "ConnectTlsEncryptedUnknownAuthority", - initString: codeBlock{code: ` - var client = new grpc.Client(); - client.load([], "../../../../lib/testutils/httpmultibin/grpc_testing/test.proto");`}, - vuString: codeBlock{ - code: fmt.Sprintf(`client.connect("GRPCBIN_ADDR", { timeout: '1s', tls: { cert: "%s", key: "%s", password: "abc321" }});`, - "-----BEGIN CERTIFICATE-----\\n"+ - "MIIBVzCB/6ADAgECAgkAg/SeNG3XqB0wCgYIKoZIzj0EAwIwEDEOMAwGA1UEAwwF\\n"+ - "TXkgQ0EwIBcNMjIwMTIxMTUxMjM0WhgPMzAyMTA1MjQxNTEyMzRaMBExDzANBgNV\\n"+ - "BAMMBmNsaWVudDBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABKM7OJQMYG4KLtDA\\n"+ - "gZ8zOg2PimHMmQnjD2HtI4cSwIUJJnvHWLowbFe9fk6XeP9b3dK1ImUI++/EZdVr\\n"+ - "ABAcngejPzA9MA4GA1UdDwEB/wQEAwIBBjAMBgNVHRMBAf8EAjAAMB0GA1UdDgQW\\n"+ - "BBSttJe1mcPEnBOZ6wvKPG4zL0m1CzAKBggqhkjOPQQDAgNHADBEAiBPSLgKA/r9\\n"+ - "u/FW6W+oy6Odm1kdNMGCI472iTn545GwJgIgb3UQPOUTOj0IN4JLJYfmYyXviqsy\\n"+ - "zk9eWNHFXDA9U6U=\\n"+ - "-----END CERTIFICATE-----", - "-----BEGIN EC PRIVATE KEY-----\\n"+ - "Proc-Type: 4,ENCRYPTED\\n"+ - "DEK-Info: AES-256-CBC,3E311E9B602231BFB5C752071EE7D652"+ - "\\n\\n"+ - "sAKeqbacug0v4ruE1A0CACwGVEGBQVOl1CiGVp5RsxgNZKXzMS6EsTTNLw378coF\\n"+ - "KXbF+he05HIuzToOz2ANLXov1iCrVpotKVB4l2obTQvg+5VET902ky99Mc9Us7jd\\n"+ - "UwW8LpXlSlhcNWuUfK6wyosL42TbcIxjqZWaESW+6ww=\\n"+ - "-----END EC PRIVATE KEY-----"), - err: "x509: decryption password incorrect", - }, - }, { name: "Connect", initString: codeBlock{code: ` @@ -988,6 +872,249 @@ func TestClient(t *testing.T) { } } +func TestClient_Connect_TlsParameters(t *testing.T) { + t.Parallel() + + testingKey := func(s string) string { + t.Helper() + return strings.ReplaceAll(s, "TESTING KEY", "PRIVATE KEY") + } + + clientCACert := []byte("-----BEGIN CERTIFICATE-----\nMIIBWzCCAQGgAwIBAgIJAIQMBgLi+DV6MAoGCCqGSM49BAMCMBAxDjAMBgNVBAMM\nBU15IENBMCAXDTIyMDEyMTEyMjkzNloYDzMwMjEwNTI0MTIyOTM2WjAQMQ4wDAYD\nVQQDDAVNeSBDQTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABHnrghULHa2hSa/C\nWimwCn42KWdlPqd6/zs3JgLIxTvBHJJlfbhWbBqtybqyovWd3QykHMIpx0NZmpYn\nG8FoWpmjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1Ud\nDgQWBBSkukBA8lgFvvBJAYKsoSUR+PX71jAKBggqhkjOPQQDAgNIADBFAiEAiFF7\nY54CMNRSBSVMgd4mQgrzJInRH88KpLsQ7VeOAaQCIEa0vaLln9zxIDZQKocml4Db\nAEJr8tDzMKIds6sRTBT4\n-----END CERTIFICATE-----") + localhostCert := "-----BEGIN CERTIFICATE-----\\nMIIDOTCCAiGgAwIBAgIQSRJrEpBGFc7tNb1fb5pKFzANBgkqhkiG9w0BAQsFADAS\\nMRAwDgYDVQQKEwdBY21lIENvMCAXDTcwMDEwMTAwMDAwMFoYDzIwODQwMTI5MTYw\\nMDAwWjASMRAwDgYDVQQKEwdBY21lIENvMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A\\nMIIBCgKCAQEA6Gba5tHV1dAKouAaXO3/ebDUU4rvwCUg/CNaJ2PT5xLD4N1Vcb8r\\nbFSW2HXKq+MPfVdwIKR/1DczEoAGf/JWQTW7EgzlXrCd3rlajEX2D73faWJekD0U\\naUgz5vtrTXZ90BQL7WvRICd7FlEZ6FPOcPlumiyNmzUqtwGhO+9ad1W5BqJaRI6P\\nYfouNkwR6Na4TzSj5BrqUfP0FwDizKSJ0XXmh8g8G9mtwxOSN3Ru1QFc61Xyeluk\\nPOGKBV/q6RBNklTNe0gI8usUMlYyoC7ytppNMW7X2vodAelSu25jgx2anj9fDVZu\\nh7AXF5+4nJS4AAt0n1lNY7nGSsdZas8PbQIDAQABo4GIMIGFMA4GA1UdDwEB/wQE\\nAwICpDATBgNVHSUEDDAKBggrBgEFBQcDATAPBgNVHRMBAf8EBTADAQH/MB0GA1Ud\\nDgQWBBStsdjh3/JCXXYlQryOrL4Sh7BW5TAuBgNVHREEJzAlggtleGFtcGxlLmNv\\nbYcEfwAAAYcQAAAAAAAAAAAAAAAAAAAAATANBgkqhkiG9w0BAQsFAAOCAQEAxWGI\\n5NhpF3nwwy/4yB4i/CwwSpLrWUa70NyhvprUBC50PxiXav1TeDzwzLx/o5HyNwsv\\ncxv3HdkLW59i/0SlJSrNnWdfZ19oTcS+6PtLoVyISgtyN6DpkKpdG1cOkW3Cy2P2\\n+tK/tKHRP1Y/Ra0RiDpOAmqn0gCOFGz8+lqDIor/T7MTpibL3IxqWfPrvfVRHL3B\\ngrw/ZQTTIVjjh4JBSW3WyWgNo/ikC1lrVxzl4iPUGptxT36Cr7Zk2Bsg0XqwbOvK\\n5d+NTDREkSnUbie4GeutujmX3Dsx88UiV6UY/4lHJa6I5leHUNOHahRbpbWeOfs/\\nWkBKOclmOV2xlTVuPw==\\n-----END CERTIFICATE-----" + localhostEncryptedKey := testingKey("-----BEGIN RSA TESTING KEY-----\\nProc-Type: 4,ENCRYPTED\\nDEK-Info: AES-256-CBC,B2557B8662FBEC979823E6F51B8ED777\\n\\n9xXt7ZCYHjYz501uiQKPLpxmz1qGNwu/u2VCwu/dFql2BLGfKrk5j4ZvaoKqUwVB\\nQfUaisSv1g++Rh13qDOOvRO38TF7aQPxImqCw2ew/fFC0JTiPWpSaQtIcWOxASpa\\ns84Z4LIolfeLxXyOG3JwWeKG/WQCMf1QNM+LXlfCJU6vdY0KeoDUcp4CkFNDdUrx\\nqGaF4xJxaQfLSSLuXlWTYmz+IlPMy/xOUCn3eSemWd4ZBdFUIwsSsuQkZHzthjJg\\nDbvAzuEzvQENXInnfYHkA4XHM+SMV+4d+aZgPNUSWv3YfXeOhapdpeAjVq6Q2TiX\\nxkFOjFYUKWO6sLWS5WfB1eqwwh9vNkZVHbmYYUvJbc2Aw0EJlQWxeQ6Vj0d6TIev\\nEPr6jFROaJ5kTd2XxG4HzJcGWsV27q4r159GGGmrZk1GjGZKImWP6Y5f3t4u+uJZ\\nEHVGx+SkilEaOM0ar1sXGgtIif741GRYYibd9hD1+0hSOxyVpehtEeb/wJRG4Vd6\\ni07ANkqwOop4K/nW5OOXKEz6eDrXAAJ2gzjN74WCyR5nJ2XoTjUa9Vo9hNrBcmtL\\ndRoeHOu9BhN+mu8YPwdisjtK6AJorsf0bQWqGpnexFw9Fq/XMpCTBT6P5X6gsNAs\\nRKvS5bwai7e9pJqZY+iJjdCTnFflDTX/r/lt4SxIkoZvGoGEVeMJsc6yFZP6yDCx\\nzjZkk0R3WsVusheATVBJJJcsHLdaUR707TU5lmhVFx0BYcBVrfKNc1h0E+alSM5R\\nij3T88ipk8BN4/e6gUpQCptMtda7wFCxiAIU+l1skGmM29ZvPB8BNAbMFCxioBGk\\nMe6QWcqOTLzoLwFHH2hSPYhZKeadCyz53OKbyIK/m06BXMVpFaIxesJZx6qW3aew\\ngShlUwr7yu9QJlxGZX0wIC1dyT69lRcLsbqzqnp6EMspSEZYvysq2k0GKZyAqLnr\\ne9CClm0wMnj45SK34/s1BWZNbBgXkDlzTKwMMN6RRY09seLoooJ64QPXgvW8T96P\\n0my7xtvtmt3h7krsudV68JbgaMotjvzV2vOxgD+s93QQIByIU+mTef5giEshERTL\\n8cOw4jp99p0wswH9hbn8TQsSf1UPFsL8P3HbD6HiNKKA1YyijgSIEQ6z0H0ALujb\\nhvweCPpwvOQAXxg+cpumn0bu7oLonWhdy+pkfYEvw/UWNX/7Qd7EIp8v84FI6J0U\\njX2iIIBm8rbA+lF10jo7GobPoQ4bGDEQOsNxuUSYvc07HoMpEVTH9Kg8dOZmvRQp\\npwyG4/o2+5LWXw8c1+1KNvdlhM8iMrCzz/0gok7UHLvisb3MruZE9c6Ujoua09Tu\\nshPGfJzXelJiRUwajFFAeBS/TPPBqi8KjFrz+sjYA8rFk7rHZZYW2p1n11Z+SLWj\\nMwBqQ5yCLohZe5UELdei8h0OuUOgfvnmJWcNk0vhlC1RxzjgUE1ZuQgD+yVbnWEu\\nXtcpRl5KCY7LnKflxpY5flhLdL0I4pH3coBcWn+87F8TCwxE6xt9Db/ny0Upoupf\\niZ1HoCyF0iJj75Duu9Ssr61gR8Gd/R6agXEhi19o517yeK7x+a+UPAinojjROQGD\\n-----END RSA TESTING KEY-----\\n") + clientCert := "-----BEGIN CERTIFICATE-----\\nMIIBVzCB/6ADAgECAgkAg/SeNG3XqB0wCgYIKoZIzj0EAwIwEDEOMAwGA1UEAwwF\\nTXkgQ0EwIBcNMjIwMTIxMTUxMjM0WhgPMzAyMTA1MjQxNTEyMzRaMBExDzANBgNV\\nBAMMBmNsaWVudDBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABKM7OJQMYG4KLtDA\\ngZ8zOg2PimHMmQnjD2HtI4cSwIUJJnvHWLowbFe9fk6XeP9b3dK1ImUI++/EZdVr\\nABAcngejPzA9MA4GA1UdDwEB/wQEAwIBBjAMBgNVHRMBAf8EAjAAMB0GA1UdDgQW\\nBBSttJe1mcPEnBOZ6wvKPG4zL0m1CzAKBggqhkjOPQQDAgNHADBEAiBPSLgKA/r9\\nu/FW6W+oy6Odm1kdNMGCI472iTn545GwJgIgb3UQPOUTOj0IN4JLJYfmYyXviqsy\\nzk9eWNHFXDA9U6U=\\n-----END CERTIFICATE-----" + clientKey := testingKey("-----BEGIN EC TESTING KEY-----\\nMHcCAQEEINDaMGkOT3thu1A0LfLJr3Jd011/aEG6OArmEQaujwgpoAoGCCqGSM49\\nAwEHoUQDQgAEozs4lAxgbgou0MCBnzM6DY+KYcyZCeMPYe0jhxLAhQkme8dYujBs\\nV71+Tpd4/1vd0rUiZQj778Rl1WsAEByeBw==\\n-----END EC TESTING KEY-----") + clientEncryptedKey := testingKey("-----BEGIN EC TESTING KEY-----\\nProc-Type: 4,ENCRYPTED\\nDEK-Info: AES-256-CBC,3E311E9B602231BFB5C752071EE7D652\\n\\nsAKeqbacug0v4ruE1A0CACwGVEGBQVOl1CiGVp5RsxgNZKXzMS6EsTTNLw378coF\\nKXbF+he05HIuzToOz2ANLXov1iCrVpotKVB4l2obTQvg+5VET902ky99Mc9Us7jd\\nUwW8LpXlSlhcNWuUfK6wyosL42TbcIxjqZWaESW+6ww=\\n-----END EC TESTING KEY-----") + trivialKeyPassword := "abc123" + clientCertNoAuth := "-----BEGIN CERTIFICATE-----\\nMIIB2TCCAX6gAwIBAgIUJIZKiR78AH2ioZ+Jae/sElgH85kwCgYIKoZIzj0EAwIw\\nEDEOMAwGA1UEAwwFTXkgQ0EwHhcNMjMwNzA3MTAyNjQ2WhcNMjQwNzA2MTAyNjQ2\\nWjARMQ8wDQYDVQQDDAZjbGllbnQwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAASj\\nOziUDGBuCi7QwIGfMzoNj4phzJkJ4w9h7SOHEsCFCSZ7x1i6MGxXvX5Ol3j/W93S\\ntSJlCPvvxGXVawAQHJ4Ho4G0MIGxMAkGA1UdEwQCMAAwEQYJYIZIAYb4QgEBBAQD\\nAgWgMCwGCWCGSAGG+EIBDQQfFh1Mb2NhbCBUZXN0IENsaWVudCBDZXJ0aWZpY2F0\\nZTAdBgNVHQ4EFgQUrbSXtZnDxJwTmesLyjxuMy9JtQswHwYDVR0jBBgwFoAUpLpA\\nQPJYBb7wSQGCrKElEfj1+9YwDgYDVR0PAQH/BAQDAgXgMBMGA1UdJQQMMAoGCCsG\\nAQUFBwMEMAoGCCqGSM49BAMCA0kAMEYCIQDcHrzug3V3WvUU+tEKhG1C4cPG5rPJ\\n/y3oOoM0roOnsgIhAP23UmiC6Qdgj+MOhXWSaNt3exWvlxdKmLm2edkxaTs+\\n-----END CERTIFICATE-----" + + type testState struct { + *modulestest.Runtime + httpBin *httpmultibin.HTTPMultiBin + samples chan metrics.SampleContainer + } + + setup := func(t *testing.T) testState { + t.Helper() + + tb := httpmultibin.NewHTTPMultiBin(t) + samples := make(chan metrics.SampleContainer, 1000) + testRuntime := modulestest.NewRuntime(t) + + cwd, err := os.Getwd() //nolint:golint,forbidigo + require.NoError(t, err) + fs := fsext.NewOsFs() + if isWindows { + fs = fsext.NewTrimFilePathSeparatorFs(fs) + } + testRuntime.VU.InitEnvField.CWD = &url.URL{Path: cwd} + testRuntime.VU.InitEnvField.FileSystems = map[string]fsext.Fs{"file": fs} + + return testState{ + Runtime: testRuntime, + httpBin: tb, + samples: samples, + } + } + + tests := []testcase{ + { + name: "ConnectTlsEmptyTlsSuccess", + initString: codeBlock{code: "var client = new grpc.Client();"}, + vuString: codeBlock{ + code: `client.connect("GRPCBIN_ADDR", { tls: { }});`, + }, + }, + { + name: "ConnectTlsInvalidTlsParamCertType", + initString: codeBlock{code: "var client = new grpc.Client();"}, + vuString: codeBlock{ + code: `client.connect("GRPCBIN_ADDR", { tls: { cert: 0 }});`, + err: `invalid grpc.connect() parameters: invalid tls cert value: 'map[string]interface {}{"cert":0}', it needs to be a PEM formatted string`, + }, + }, + { + name: "ConnectTlsInvalidTlsParamKeyType", + initString: codeBlock{code: "var client = new grpc.Client();"}, + vuString: codeBlock{ + code: `client.connect("GRPCBIN_ADDR", { tls: { cert: "", key: 0 }});`, + err: `invalid grpc.connect() parameters: invalid tls key value: 'map[string]interface {}{"cert":"", "key":0}', it needs to be a PEM formatted string`, + }, + }, + { + name: "ConnectTlsInvalidTlsParamPasswordType", + initString: codeBlock{code: "var client = new grpc.Client();"}, + vuString: codeBlock{ + code: `client.connect("GRPCBIN_ADDR", { tls: { cert: "", key: "", password: 0 }});`, + err: `invalid grpc.connect() parameters: invalid tls password value: 'map[string]interface {}{"cert":"", "key":"", "password":0}', it needs to be a string`, + }, + }, + { + name: "ConnectTlsInvalidTlsParamCACertsType", + initString: codeBlock{code: "var client = new grpc.Client();"}, + vuString: codeBlock{ + code: `client.connect("GRPCBIN_ADDR", { tls: { cert: "", key: "", cacerts: 0 }});`, + err: `invalid grpc.connect() parameters: invalid tls cacerts value: 'map[string]interface {}{"cacerts":0, "cert":"", "key":""}', it needs to be a string or string[] of PEM formatted strings`, + }, + }, + { + name: "ConnectTls", + setup: func(tb *httpmultibin.HTTPMultiBin) { + clientCAPool := x509.NewCertPool() + clientCAPool.AppendCertsFromPEM(clientCACert) + tb.ServerHTTP2.TLS.ClientAuth = tls.RequireAndVerifyClientCert + tb.ServerHTTP2.TLS.ClientCAs = clientCAPool + }, + initString: codeBlock{code: "var client = new grpc.Client();"}, + vuString: codeBlock{code: fmt.Sprintf(`client.connect("GRPCBIN_ADDR", { timeout: '2s', tls: { cacerts: "%s", cert: "%s", key: "%s" }});`, localhostCert, clientCert, clientKey)}, + }, + { + name: "ConnectTlsEncryptedKey", + initString: codeBlock{code: "var client = new grpc.Client();"}, + vuString: codeBlock{code: fmt.Sprintf(`client.connect("GRPCBIN_ADDR", { tls: { cacerts: ["%s"], cert: "%[1]s", key: "%s", password: "%s" }});`, localhostCert, localhostEncryptedKey, trivialKeyPassword)}, + }, + { + name: "ConnectTlsEncryptedKeyDecryptionFailed", + initString: codeBlock{code: "var client = new grpc.Client();"}, + vuString: codeBlock{ + code: fmt.Sprintf(`client.connect("GRPCBIN_ADDR", { timeout: '1s', tls: { cert: "%s", key: "%s", password: "abc321" }});`, + clientCert, + clientEncryptedKey, + ), + err: "x509: decryption password incorrect", + }, + }, + { + name: "ConnectTlsClientCertNoClientAuth", + setup: func(tb *httpmultibin.HTTPMultiBin) { + clientCAPool := x509.NewCertPool() + clientCAPool.AppendCertsFromPEM(clientCACert) + tb.ServerHTTP2.TLS.ClientAuth = tls.RequireAndVerifyClientCert + tb.ServerHTTP2.TLS.ClientCAs = clientCAPool + }, + initString: codeBlock{code: `var client = new grpc.Client();`}, + vuString: codeBlock{ + code: fmt.Sprintf(`client.connect("GRPCBIN_ADDR", { timeout: '2s', tls: { cacerts: ["%s"], cert: "%s", key: "%s" }});`, + localhostCert, + clientCertNoAuth, + clientKey), + err: "remote error: tls: bad certificate", + }, + }, + { + name: "ConnectTlsClientCertWithPasswordNoClientAuth", + setup: func(tb *httpmultibin.HTTPMultiBin) { + clientCAPool := x509.NewCertPool() + clientCAPool.AppendCertsFromPEM(clientCACert) + tb.ServerHTTP2.TLS.ClientAuth = tls.RequireAndVerifyClientCert + tb.ServerHTTP2.TLS.ClientCAs = clientCAPool + }, + initString: codeBlock{code: `var client = new grpc.Client();`}, + vuString: codeBlock{ + code: fmt.Sprintf(` + client.connect("GRPCBIN_ADDR", { timeout: '1s', tls: { cacerts: ["%s"], cert: "%s", key: "%s", password: "abc123" }}); + `, + localhostCert, + clientCertNoAuth, + clientEncryptedKey), + err: "remote error: tls: bad certificate", + }, + }, + { + name: "ConnectTlsInvokeSuccess", + setup: func(tb *httpmultibin.HTTPMultiBin) { + clientCAPool := x509.NewCertPool() + clientCAPool.AppendCertsFromPEM(clientCACert) + tb.ServerHTTP2.TLS.ClientAuth = tls.RequireAndVerifyClientCert + tb.ServerHTTP2.TLS.ClientCAs = clientCAPool + tb.GRPCStub.EmptyCallFunc = func(context.Context, *grpc_testing.Empty) (*grpc_testing.Empty, error) { + return &grpc_testing.Empty{}, nil + } + }, + initString: codeBlock{code: ` + var client = new grpc.Client(); + client.load([], "../../../../lib/testutils/httpmultibin/grpc_testing/test.proto");`}, + vuString: codeBlock{ + code: fmt.Sprintf(` + client.connect("GRPCBIN_ADDR", { timeout: '1s', tls: { cacerts: ["%s"], cert: "%s", key: "%s" }}); + var resp = client.invoke("grpc.testing.TestService/EmptyCall", {}) + if (resp.status !== grpc.StatusOK) { + throw new Error("unexpected error: " + JSON.stringify(resp.error) + "or status: " + resp.status) + }`, + localhostCert, + clientCert, + clientKey), + }, + }, + } + assertResponse := func(t *testing.T, cb codeBlock, err error, val goja.Value, ts testState) { + if isWindows && cb.windowsErr != "" && err != nil { + err = errors.New(strings.ReplaceAll(err.Error(), cb.windowsErr, cb.err)) + } + if cb.err == "" { + assert.NoError(t, err) + } else { + require.Error(t, err) + assert.Contains(t, err.Error(), cb.err) + } + if cb.val != nil { + require.NotNil(t, val) + assert.Equal(t, cb.val, val.Export()) + } + if cb.asserts != nil { + cb.asserts(t, ts.httpBin, ts.samples, err) + } + } + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + ts := setup(t) + + m, ok := New().NewModuleInstance(ts.VU).(*ModuleInstance) + require.True(t, ok) + require.NoError(t, ts.VU.Runtime().Set("grpc", m.Exports().Named)) + + // setup necessary environment if needed by a test + if tt.setup != nil { + tt.setup(ts.httpBin) + } + + replace := func(code string) (goja.Value, error) { + return ts.VU.Runtime().RunString(ts.httpBin.Replacer.Replace(code)) + } + + val, err := replace(tt.initString.code) + assertResponse(t, tt.initString, err, val, ts) + + registry := metrics.NewRegistry() + root, err := lib.NewGroup("", nil) + require.NoError(t, err) + + state := &lib.State{ + Group: root, + Dialer: ts.httpBin.Dialer, + TLSConfig: ts.httpBin.TLSClientConfig, + Samples: ts.samples, + Options: lib.Options{ + SystemTags: metrics.NewSystemTagSet( + metrics.TagName, + metrics.TagURL, + ), + UserAgent: null.StringFrom("k6-test"), + }, + BuiltinMetrics: metrics.RegisterBuiltinMetrics(registry), + Tags: lib.NewVUStateTags(registry.RootTagSet()), + } + ts.MoveToVUContext(state) + val, err = replace(tt.vuString.code) + assertResponse(t, tt.vuString, err, val, ts) + }) + } +} + func TestDebugStat(t *testing.T) { t.Parallel() diff --git a/lib/testutils/httpmultibin/httpmultibin.go b/lib/testutils/httpmultibin/httpmultibin.go index 8c1015bb8f4..fac0cc3699f 100644 --- a/lib/testutils/httpmultibin/httpmultibin.go +++ b/lib/testutils/httpmultibin/httpmultibin.go @@ -67,92 +67,6 @@ const httpDomain = "httpbin.local" // https://golang.org/src/net/http/internal/testcert.go?s=399:410#L10 const httpsDomain = "example.com" -const localhostCert = `-----BEGIN CERTIFICATE----- -MIIDOTCCAiGgAwIBAgIQSRJrEpBGFc7tNb1fb5pKFzANBgkqhkiG9w0BAQsFADAS -MRAwDgYDVQQKEwdBY21lIENvMCAXDTcwMDEwMTAwMDAwMFoYDzIwODQwMTI5MTYw -MDAwWjASMRAwDgYDVQQKEwdBY21lIENvMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A -MIIBCgKCAQEA6Gba5tHV1dAKouAaXO3/ebDUU4rvwCUg/CNaJ2PT5xLD4N1Vcb8r -bFSW2HXKq+MPfVdwIKR/1DczEoAGf/JWQTW7EgzlXrCd3rlajEX2D73faWJekD0U -aUgz5vtrTXZ90BQL7WvRICd7FlEZ6FPOcPlumiyNmzUqtwGhO+9ad1W5BqJaRI6P -YfouNkwR6Na4TzSj5BrqUfP0FwDizKSJ0XXmh8g8G9mtwxOSN3Ru1QFc61Xyeluk -POGKBV/q6RBNklTNe0gI8usUMlYyoC7ytppNMW7X2vodAelSu25jgx2anj9fDVZu -h7AXF5+4nJS4AAt0n1lNY7nGSsdZas8PbQIDAQABo4GIMIGFMA4GA1UdDwEB/wQE -AwICpDATBgNVHSUEDDAKBggrBgEFBQcDATAPBgNVHRMBAf8EBTADAQH/MB0GA1Ud -DgQWBBStsdjh3/JCXXYlQryOrL4Sh7BW5TAuBgNVHREEJzAlggtleGFtcGxlLmNv -bYcEfwAAAYcQAAAAAAAAAAAAAAAAAAAAATANBgkqhkiG9w0BAQsFAAOCAQEAxWGI -5NhpF3nwwy/4yB4i/CwwSpLrWUa70NyhvprUBC50PxiXav1TeDzwzLx/o5HyNwsv -cxv3HdkLW59i/0SlJSrNnWdfZ19oTcS+6PtLoVyISgtyN6DpkKpdG1cOkW3Cy2P2 -+tK/tKHRP1Y/Ra0RiDpOAmqn0gCOFGz8+lqDIor/T7MTpibL3IxqWfPrvfVRHL3B -grw/ZQTTIVjjh4JBSW3WyWgNo/ikC1lrVxzl4iPUGptxT36Cr7Zk2Bsg0XqwbOvK -5d+NTDREkSnUbie4GeutujmX3Dsx88UiV6UY/4lHJa6I5leHUNOHahRbpbWeOfs/ -WkBKOclmOV2xlTVuPw== ------END CERTIFICATE-----` - -var localhostKey = testingKey(`-----BEGIN RSA TESTING KEY----- -MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQDoZtrm0dXV0Aqi -4Bpc7f95sNRTiu/AJSD8I1onY9PnEsPg3VVxvytsVJbYdcqr4w99V3AgpH/UNzMS -gAZ/8lZBNbsSDOVesJ3euVqMRfYPvd9pYl6QPRRpSDPm+2tNdn3QFAvta9EgJ3sW -URnoU85w+W6aLI2bNSq3AaE771p3VbkGolpEjo9h+i42TBHo1rhPNKPkGupR8/QX -AOLMpInRdeaHyDwb2a3DE5I3dG7VAVzrVfJ6W6Q84YoFX+rpEE2SVM17SAjy6xQy -VjKgLvK2mk0xbtfa+h0B6VK7bmODHZqeP18NVm6HsBcXn7iclLgAC3SfWU1jucZK -x1lqzw9tAgMBAAECggEABWzxS1Y2wckblnXY57Z+sl6YdmLV+gxj2r8Qib7g4ZIk -lIlWR1OJNfw7kU4eryib4fc6nOh6O4AWZyYqAK6tqNQSS/eVG0LQTLTTEldHyVJL -dvBe+MsUQOj4nTndZW+QvFzbcm2D8lY5n2nBSxU5ypVoKZ1EqQzytFcLZpTN7d89 -EPj0qDyrV4NZlWAwL1AygCwnlwhMQjXEalVF1ylXwU3QzyZ/6MgvF6d3SSUlh+sq -XefuyigXw484cQQgbzopv6niMOmGP3of+yV4JQqUSb3IDmmT68XjGd2Dkxl4iPki -6ZwXf3CCi+c+i/zVEcufgZ3SLf8D99kUGE7v7fZ6AQKBgQD1ZX3RAla9hIhxCf+O -3D+I1j2LMrdjAh0ZKKqwMR4JnHX3mjQI6LwqIctPWTU8wYFECSh9klEclSdCa64s -uI/GNpcqPXejd0cAAdqHEEeG5sHMDt0oFSurL4lyud0GtZvwlzLuwEweuDtvT9cJ -Wfvl86uyO36IW8JdvUprYDctrQKBgQDycZ697qutBieZlGkHpnYWUAeImVA878sJ -w44NuXHvMxBPz+lbJGAg8Cn8fcxNAPqHIraK+kx3po8cZGQywKHUWsxi23ozHoxo -+bGqeQb9U661TnfdDspIXia+xilZt3mm5BPzOUuRqlh4Y9SOBpSWRmEhyw76w4ZP -OPxjWYAgwQKBgA/FehSYxeJgRjSdo+MWnK66tjHgDJE8bYpUZsP0JC4R9DL5oiaA -brd2fI6Y+SbyeNBallObt8LSgzdtnEAbjIH8uDJqyOmknNePRvAvR6mP4xyuR+Bv -m+Lgp0DMWTw5J9CKpydZDItc49T/mJ5tPhdFVd+am0NAQnmr1MCZ6nHxAoGABS3Y -LkaC9FdFUUqSU8+Chkd/YbOkuyiENdkvl6t2e52jo5DVc1T7mLiIrRQi4SI8N9bN -/3oJWCT+uaSLX2ouCtNFunblzWHBrhxnZzTeqVq4SLc8aESAnbslKL4i8/+vYZlN -s8xtiNcSvL+lMsOBORSXzpj/4Ot8WwTkn1qyGgECgYBKNTypzAHeLE6yVadFp3nQ -Ckq9yzvP/ib05rvgbvrne00YeOxqJ9gtTrzgh7koqJyX1L4NwdkEza4ilDWpucn0 -xiUZS4SoaJq6ZvcBYS62Yr1t8n09iG47YL8ibgtmH3L+svaotvpVxVK+d7BLevA/ -ZboOWVe3icTy64BT3OQhmg== ------END RSA TESTING KEY-----`) - -// trivial password: abc123 -var localhostEncryptedKey = testingKey(`-----BEGIN RSA TESTING KEY----- -Proc-Type: 4,ENCRYPTED -DEK-Info: AES-256-CBC,B2557B8662FBEC979823E6F51B8ED777 - -9xXt7ZCYHjYz501uiQKPLpxmz1qGNwu/u2VCwu/dFql2BLGfKrk5j4ZvaoKqUwVB -QfUaisSv1g++Rh13qDOOvRO38TF7aQPxImqCw2ew/fFC0JTiPWpSaQtIcWOxASpa -s84Z4LIolfeLxXyOG3JwWeKG/WQCMf1QNM+LXlfCJU6vdY0KeoDUcp4CkFNDdUrx -qGaF4xJxaQfLSSLuXlWTYmz+IlPMy/xOUCn3eSemWd4ZBdFUIwsSsuQkZHzthjJg -DbvAzuEzvQENXInnfYHkA4XHM+SMV+4d+aZgPNUSWv3YfXeOhapdpeAjVq6Q2TiX -xkFOjFYUKWO6sLWS5WfB1eqwwh9vNkZVHbmYYUvJbc2Aw0EJlQWxeQ6Vj0d6TIev -EPr6jFROaJ5kTd2XxG4HzJcGWsV27q4r159GGGmrZk1GjGZKImWP6Y5f3t4u+uJZ -EHVGx+SkilEaOM0ar1sXGgtIif741GRYYibd9hD1+0hSOxyVpehtEeb/wJRG4Vd6 -i07ANkqwOop4K/nW5OOXKEz6eDrXAAJ2gzjN74WCyR5nJ2XoTjUa9Vo9hNrBcmtL -dRoeHOu9BhN+mu8YPwdisjtK6AJorsf0bQWqGpnexFw9Fq/XMpCTBT6P5X6gsNAs -RKvS5bwai7e9pJqZY+iJjdCTnFflDTX/r/lt4SxIkoZvGoGEVeMJsc6yFZP6yDCx -zjZkk0R3WsVusheATVBJJJcsHLdaUR707TU5lmhVFx0BYcBVrfKNc1h0E+alSM5R -ij3T88ipk8BN4/e6gUpQCptMtda7wFCxiAIU+l1skGmM29ZvPB8BNAbMFCxioBGk -Me6QWcqOTLzoLwFHH2hSPYhZKeadCyz53OKbyIK/m06BXMVpFaIxesJZx6qW3aew -gShlUwr7yu9QJlxGZX0wIC1dyT69lRcLsbqzqnp6EMspSEZYvysq2k0GKZyAqLnr -e9CClm0wMnj45SK34/s1BWZNbBgXkDlzTKwMMN6RRY09seLoooJ64QPXgvW8T96P -0my7xtvtmt3h7krsudV68JbgaMotjvzV2vOxgD+s93QQIByIU+mTef5giEshERTL -8cOw4jp99p0wswH9hbn8TQsSf1UPFsL8P3HbD6HiNKKA1YyijgSIEQ6z0H0ALujb -hvweCPpwvOQAXxg+cpumn0bu7oLonWhdy+pkfYEvw/UWNX/7Qd7EIp8v84FI6J0U -jX2iIIBm8rbA+lF10jo7GobPoQ4bGDEQOsNxuUSYvc07HoMpEVTH9Kg8dOZmvRQp -pwyG4/o2+5LWXw8c1+1KNvdlhM8iMrCzz/0gok7UHLvisb3MruZE9c6Ujoua09Tu -shPGfJzXelJiRUwajFFAeBS/TPPBqi8KjFrz+sjYA8rFk7rHZZYW2p1n11Z+SLWj -MwBqQ5yCLohZe5UELdei8h0OuUOgfvnmJWcNk0vhlC1RxzjgUE1ZuQgD+yVbnWEu -XtcpRl5KCY7LnKflxpY5flhLdL0I4pH3coBcWn+87F8TCwxE6xt9Db/ny0Upoupf -iZ1HoCyF0iJj75Duu9Ssr61gR8Gd/R6agXEhi19o517yeK7x+a+UPAinojjROQGD ------END RSA TESTING KEY----- -`) - -// Because security scans about "leaked keys". The above keys are _not_ "leaked keys". -func testingKey(s string) string { return strings.ReplaceAll(s, "TESTING KEY", "PRIVATE KEY") } - // HTTPMultiBin can be used as a local alternative of httpbin.org. It offers both http and https servers, as well as real domains type HTTPMultiBin struct { Mux *http.ServeMux @@ -481,9 +395,6 @@ func NewHTTPMultiBin(t testing.TB) *HTTPMultiBin { "HTTP2BIN_PORT", http2URL.Port(), "GRPCBIN_ADDR", fmt.Sprintf("%s:%s", httpsDomain, http2URL.Port()), - "LOCALHOST_CERT", strings.ReplaceAll(localhostCert, "\n", "\\n"), - "LOCALHOST_KEY", strings.ReplaceAll(localhostKey, "\n", "\\n"), - "LOCALHOST_ENCRYPTED_KEY", strings.ReplaceAll(localhostEncryptedKey, "\n", "\\n"), ), TLSClientConfig: tlsConfig, Dialer: dialer, From a5df2fef096dc2fad979c5f4240d826691d47768 Mon Sep 17 00:00:00 2001 From: Chris Moran <116747924+chrismoran-mica@users.noreply.github.com> Date: Fri, 7 Jul 2023 08:23:56 -0400 Subject: [PATCH 13/24] Update js/modules/k6/grpc/client.go Update js/modules/k6/grpc/client.go lib/options.go should also be updated Co-authored-by: Mihail Stoykov <312246+mstoykov@users.noreply.github.com> --- js/modules/k6/grpc/client.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/modules/k6/grpc/client.go b/js/modules/k6/grpc/client.go index d9e8f3f5e18..3e91984063e 100644 --- a/js/modules/k6/grpc/client.go +++ b/js/modules/k6/grpc/client.go @@ -124,7 +124,7 @@ func decryptPrivateKey(key, password []byte) ([]byte, error) { } /* Even though `DecryptPEMBlock` has been deprecated since 1.16.x it is still - being used here because it is deprecated due to it not supporting *good* crypography + being used here because it is deprecated due to it not supporting *good* cryptography ultimately though we want to support something so we will be using it for now. */ decryptedKey, err := x509.DecryptPEMBlock(block, password) //nolint:staticcheck From 135050c884fc157b79d7e595a1107fe80aaa83ee Mon Sep 17 00:00:00 2001 From: Chris Moran Date: Fri, 7 Jul 2023 08:29:26 -0400 Subject: [PATCH 14/24] TestClient_Connect_TlsParameters renamed for clarity --- js/modules/k6/grpc/client_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/modules/k6/grpc/client_test.go b/js/modules/k6/grpc/client_test.go index 2e001b89202..d7cc06433af 100644 --- a/js/modules/k6/grpc/client_test.go +++ b/js/modules/k6/grpc/client_test.go @@ -872,7 +872,7 @@ func TestClient(t *testing.T) { } } -func TestClient_Connect_TlsParameters(t *testing.T) { +func TestClient_TlsParameters(t *testing.T) { t.Parallel() testingKey := func(s string) string { From 514ea16c5227796dd5ab976a277b25efa8dff06b Mon Sep 17 00:00:00 2001 From: Chris Moran Date: Fri, 7 Jul 2023 09:41:15 -0400 Subject: [PATCH 15/24] Give CI some more time to verify --- js/modules/k6/grpc/client_test.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/js/modules/k6/grpc/client_test.go b/js/modules/k6/grpc/client_test.go index d7cc06433af..4a5891422c0 100644 --- a/js/modules/k6/grpc/client_test.go +++ b/js/modules/k6/grpc/client_test.go @@ -967,7 +967,7 @@ func TestClient_TlsParameters(t *testing.T) { tb.ServerHTTP2.TLS.ClientCAs = clientCAPool }, initString: codeBlock{code: "var client = new grpc.Client();"}, - vuString: codeBlock{code: fmt.Sprintf(`client.connect("GRPCBIN_ADDR", { timeout: '2s', tls: { cacerts: "%s", cert: "%s", key: "%s" }});`, localhostCert, clientCert, clientKey)}, + vuString: codeBlock{code: fmt.Sprintf(`client.connect("GRPCBIN_ADDR", { tls: { cacerts: "%s", cert: "%s", key: "%s" }});`, localhostCert, clientCert, clientKey)}, }, { name: "ConnectTlsEncryptedKey", @@ -978,7 +978,7 @@ func TestClient_TlsParameters(t *testing.T) { name: "ConnectTlsEncryptedKeyDecryptionFailed", initString: codeBlock{code: "var client = new grpc.Client();"}, vuString: codeBlock{ - code: fmt.Sprintf(`client.connect("GRPCBIN_ADDR", { timeout: '1s', tls: { cert: "%s", key: "%s", password: "abc321" }});`, + code: fmt.Sprintf(`client.connect("GRPCBIN_ADDR", { timeout: '5s', tls: { cert: "%s", key: "%s", password: "abc321" }});`, clientCert, clientEncryptedKey, ), @@ -995,7 +995,7 @@ func TestClient_TlsParameters(t *testing.T) { }, initString: codeBlock{code: `var client = new grpc.Client();`}, vuString: codeBlock{ - code: fmt.Sprintf(`client.connect("GRPCBIN_ADDR", { timeout: '2s', tls: { cacerts: ["%s"], cert: "%s", key: "%s" }});`, + code: fmt.Sprintf(`client.connect("GRPCBIN_ADDR", { timeout: '5s', tls: { cacerts: ["%s"], cert: "%s", key: "%s" }});`, localhostCert, clientCertNoAuth, clientKey), @@ -1013,7 +1013,7 @@ func TestClient_TlsParameters(t *testing.T) { initString: codeBlock{code: `var client = new grpc.Client();`}, vuString: codeBlock{ code: fmt.Sprintf(` - client.connect("GRPCBIN_ADDR", { timeout: '1s', tls: { cacerts: ["%s"], cert: "%s", key: "%s", password: "abc123" }}); + client.connect("GRPCBIN_ADDR", { timeout: '5s', tls: { cacerts: ["%s"], cert: "%s", key: "%s", password: "abc123" }}); `, localhostCert, clientCertNoAuth, @@ -1037,7 +1037,7 @@ func TestClient_TlsParameters(t *testing.T) { client.load([], "../../../../lib/testutils/httpmultibin/grpc_testing/test.proto");`}, vuString: codeBlock{ code: fmt.Sprintf(` - client.connect("GRPCBIN_ADDR", { timeout: '1s', tls: { cacerts: ["%s"], cert: "%s", key: "%s" }}); + client.connect("GRPCBIN_ADDR", { timeout: '5s', tls: { cacerts: ["%s"], cert: "%s", key: "%s" }}); var resp = client.invoke("grpc.testing.TestService/EmptyCall", {}) if (resp.status !== grpc.StatusOK) { throw new Error("unexpected error: " + JSON.stringify(resp.error) + "or status: " + resp.status) From a1bf3d931908ea0913a53061c8d5e543d055d090 Mon Sep 17 00:00:00 2001 From: Chris Moran Date: Sat, 8 Jul 2023 19:33:35 -0400 Subject: [PATCH 16/24] client_test housekeeping --- js/modules/k6/grpc/client_test.go | 59 +++++++++++++++++-------------- 1 file changed, 33 insertions(+), 26 deletions(-) diff --git a/js/modules/k6/grpc/client_test.go b/js/modules/k6/grpc/client_test.go index 4a5891422c0..91b53810c72 100644 --- a/js/modules/k6/grpc/client_test.go +++ b/js/modules/k6/grpc/client_test.go @@ -880,14 +880,14 @@ func TestClient_TlsParameters(t *testing.T) { return strings.ReplaceAll(s, "TESTING KEY", "PRIVATE KEY") } - clientCACert := []byte("-----BEGIN CERTIFICATE-----\nMIIBWzCCAQGgAwIBAgIJAIQMBgLi+DV6MAoGCCqGSM49BAMCMBAxDjAMBgNVBAMM\nBU15IENBMCAXDTIyMDEyMTEyMjkzNloYDzMwMjEwNTI0MTIyOTM2WjAQMQ4wDAYD\nVQQDDAVNeSBDQTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABHnrghULHa2hSa/C\nWimwCn42KWdlPqd6/zs3JgLIxTvBHJJlfbhWbBqtybqyovWd3QykHMIpx0NZmpYn\nG8FoWpmjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1Ud\nDgQWBBSkukBA8lgFvvBJAYKsoSUR+PX71jAKBggqhkjOPQQDAgNIADBFAiEAiFF7\nY54CMNRSBSVMgd4mQgrzJInRH88KpLsQ7VeOAaQCIEa0vaLln9zxIDZQKocml4Db\nAEJr8tDzMKIds6sRTBT4\n-----END CERTIFICATE-----") - localhostCert := "-----BEGIN CERTIFICATE-----\\nMIIDOTCCAiGgAwIBAgIQSRJrEpBGFc7tNb1fb5pKFzANBgkqhkiG9w0BAQsFADAS\\nMRAwDgYDVQQKEwdBY21lIENvMCAXDTcwMDEwMTAwMDAwMFoYDzIwODQwMTI5MTYw\\nMDAwWjASMRAwDgYDVQQKEwdBY21lIENvMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A\\nMIIBCgKCAQEA6Gba5tHV1dAKouAaXO3/ebDUU4rvwCUg/CNaJ2PT5xLD4N1Vcb8r\\nbFSW2HXKq+MPfVdwIKR/1DczEoAGf/JWQTW7EgzlXrCd3rlajEX2D73faWJekD0U\\naUgz5vtrTXZ90BQL7WvRICd7FlEZ6FPOcPlumiyNmzUqtwGhO+9ad1W5BqJaRI6P\\nYfouNkwR6Na4TzSj5BrqUfP0FwDizKSJ0XXmh8g8G9mtwxOSN3Ru1QFc61Xyeluk\\nPOGKBV/q6RBNklTNe0gI8usUMlYyoC7ytppNMW7X2vodAelSu25jgx2anj9fDVZu\\nh7AXF5+4nJS4AAt0n1lNY7nGSsdZas8PbQIDAQABo4GIMIGFMA4GA1UdDwEB/wQE\\nAwICpDATBgNVHSUEDDAKBggrBgEFBQcDATAPBgNVHRMBAf8EBTADAQH/MB0GA1Ud\\nDgQWBBStsdjh3/JCXXYlQryOrL4Sh7BW5TAuBgNVHREEJzAlggtleGFtcGxlLmNv\\nbYcEfwAAAYcQAAAAAAAAAAAAAAAAAAAAATANBgkqhkiG9w0BAQsFAAOCAQEAxWGI\\n5NhpF3nwwy/4yB4i/CwwSpLrWUa70NyhvprUBC50PxiXav1TeDzwzLx/o5HyNwsv\\ncxv3HdkLW59i/0SlJSrNnWdfZ19oTcS+6PtLoVyISgtyN6DpkKpdG1cOkW3Cy2P2\\n+tK/tKHRP1Y/Ra0RiDpOAmqn0gCOFGz8+lqDIor/T7MTpibL3IxqWfPrvfVRHL3B\\ngrw/ZQTTIVjjh4JBSW3WyWgNo/ikC1lrVxzl4iPUGptxT36Cr7Zk2Bsg0XqwbOvK\\n5d+NTDREkSnUbie4GeutujmX3Dsx88UiV6UY/4lHJa6I5leHUNOHahRbpbWeOfs/\\nWkBKOclmOV2xlTVuPw==\\n-----END CERTIFICATE-----" - localhostEncryptedKey := testingKey("-----BEGIN RSA TESTING KEY-----\\nProc-Type: 4,ENCRYPTED\\nDEK-Info: AES-256-CBC,B2557B8662FBEC979823E6F51B8ED777\\n\\n9xXt7ZCYHjYz501uiQKPLpxmz1qGNwu/u2VCwu/dFql2BLGfKrk5j4ZvaoKqUwVB\\nQfUaisSv1g++Rh13qDOOvRO38TF7aQPxImqCw2ew/fFC0JTiPWpSaQtIcWOxASpa\\ns84Z4LIolfeLxXyOG3JwWeKG/WQCMf1QNM+LXlfCJU6vdY0KeoDUcp4CkFNDdUrx\\nqGaF4xJxaQfLSSLuXlWTYmz+IlPMy/xOUCn3eSemWd4ZBdFUIwsSsuQkZHzthjJg\\nDbvAzuEzvQENXInnfYHkA4XHM+SMV+4d+aZgPNUSWv3YfXeOhapdpeAjVq6Q2TiX\\nxkFOjFYUKWO6sLWS5WfB1eqwwh9vNkZVHbmYYUvJbc2Aw0EJlQWxeQ6Vj0d6TIev\\nEPr6jFROaJ5kTd2XxG4HzJcGWsV27q4r159GGGmrZk1GjGZKImWP6Y5f3t4u+uJZ\\nEHVGx+SkilEaOM0ar1sXGgtIif741GRYYibd9hD1+0hSOxyVpehtEeb/wJRG4Vd6\\ni07ANkqwOop4K/nW5OOXKEz6eDrXAAJ2gzjN74WCyR5nJ2XoTjUa9Vo9hNrBcmtL\\ndRoeHOu9BhN+mu8YPwdisjtK6AJorsf0bQWqGpnexFw9Fq/XMpCTBT6P5X6gsNAs\\nRKvS5bwai7e9pJqZY+iJjdCTnFflDTX/r/lt4SxIkoZvGoGEVeMJsc6yFZP6yDCx\\nzjZkk0R3WsVusheATVBJJJcsHLdaUR707TU5lmhVFx0BYcBVrfKNc1h0E+alSM5R\\nij3T88ipk8BN4/e6gUpQCptMtda7wFCxiAIU+l1skGmM29ZvPB8BNAbMFCxioBGk\\nMe6QWcqOTLzoLwFHH2hSPYhZKeadCyz53OKbyIK/m06BXMVpFaIxesJZx6qW3aew\\ngShlUwr7yu9QJlxGZX0wIC1dyT69lRcLsbqzqnp6EMspSEZYvysq2k0GKZyAqLnr\\ne9CClm0wMnj45SK34/s1BWZNbBgXkDlzTKwMMN6RRY09seLoooJ64QPXgvW8T96P\\n0my7xtvtmt3h7krsudV68JbgaMotjvzV2vOxgD+s93QQIByIU+mTef5giEshERTL\\n8cOw4jp99p0wswH9hbn8TQsSf1UPFsL8P3HbD6HiNKKA1YyijgSIEQ6z0H0ALujb\\nhvweCPpwvOQAXxg+cpumn0bu7oLonWhdy+pkfYEvw/UWNX/7Qd7EIp8v84FI6J0U\\njX2iIIBm8rbA+lF10jo7GobPoQ4bGDEQOsNxuUSYvc07HoMpEVTH9Kg8dOZmvRQp\\npwyG4/o2+5LWXw8c1+1KNvdlhM8iMrCzz/0gok7UHLvisb3MruZE9c6Ujoua09Tu\\nshPGfJzXelJiRUwajFFAeBS/TPPBqi8KjFrz+sjYA8rFk7rHZZYW2p1n11Z+SLWj\\nMwBqQ5yCLohZe5UELdei8h0OuUOgfvnmJWcNk0vhlC1RxzjgUE1ZuQgD+yVbnWEu\\nXtcpRl5KCY7LnKflxpY5flhLdL0I4pH3coBcWn+87F8TCwxE6xt9Db/ny0Upoupf\\niZ1HoCyF0iJj75Duu9Ssr61gR8Gd/R6agXEhi19o517yeK7x+a+UPAinojjROQGD\\n-----END RSA TESTING KEY-----\\n") - clientCert := "-----BEGIN CERTIFICATE-----\\nMIIBVzCB/6ADAgECAgkAg/SeNG3XqB0wCgYIKoZIzj0EAwIwEDEOMAwGA1UEAwwF\\nTXkgQ0EwIBcNMjIwMTIxMTUxMjM0WhgPMzAyMTA1MjQxNTEyMzRaMBExDzANBgNV\\nBAMMBmNsaWVudDBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABKM7OJQMYG4KLtDA\\ngZ8zOg2PimHMmQnjD2HtI4cSwIUJJnvHWLowbFe9fk6XeP9b3dK1ImUI++/EZdVr\\nABAcngejPzA9MA4GA1UdDwEB/wQEAwIBBjAMBgNVHRMBAf8EAjAAMB0GA1UdDgQW\\nBBSttJe1mcPEnBOZ6wvKPG4zL0m1CzAKBggqhkjOPQQDAgNHADBEAiBPSLgKA/r9\\nu/FW6W+oy6Odm1kdNMGCI472iTn545GwJgIgb3UQPOUTOj0IN4JLJYfmYyXviqsy\\nzk9eWNHFXDA9U6U=\\n-----END CERTIFICATE-----" - clientKey := testingKey("-----BEGIN EC TESTING KEY-----\\nMHcCAQEEINDaMGkOT3thu1A0LfLJr3Jd011/aEG6OArmEQaujwgpoAoGCCqGSM49\\nAwEHoUQDQgAEozs4lAxgbgou0MCBnzM6DY+KYcyZCeMPYe0jhxLAhQkme8dYujBs\\nV71+Tpd4/1vd0rUiZQj778Rl1WsAEByeBw==\\n-----END EC TESTING KEY-----") - clientEncryptedKey := testingKey("-----BEGIN EC TESTING KEY-----\\nProc-Type: 4,ENCRYPTED\\nDEK-Info: AES-256-CBC,3E311E9B602231BFB5C752071EE7D652\\n\\nsAKeqbacug0v4ruE1A0CACwGVEGBQVOl1CiGVp5RsxgNZKXzMS6EsTTNLw378coF\\nKXbF+he05HIuzToOz2ANLXov1iCrVpotKVB4l2obTQvg+5VET902ky99Mc9Us7jd\\nUwW8LpXlSlhcNWuUfK6wyosL42TbcIxjqZWaESW+6ww=\\n-----END EC TESTING KEY-----") + clientAuthCA := []byte("-----BEGIN CERTIFICATE-----\nMIIBWzCCAQGgAwIBAgIJAIQMBgLi+DV6MAoGCCqGSM49BAMCMBAxDjAMBgNVBAMM\nBU15IENBMCAXDTIyMDEyMTEyMjkzNloYDzMwMjEwNTI0MTIyOTM2WjAQMQ4wDAYD\nVQQDDAVNeSBDQTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABHnrghULHa2hSa/C\nWimwCn42KWdlPqd6/zs3JgLIxTvBHJJlfbhWbBqtybqyovWd3QykHMIpx0NZmpYn\nG8FoWpmjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1Ud\nDgQWBBSkukBA8lgFvvBJAYKsoSUR+PX71jAKBggqhkjOPQQDAgNIADBFAiEAiFF7\nY54CMNRSBSVMgd4mQgrzJInRH88KpLsQ7VeOAaQCIEa0vaLln9zxIDZQKocml4Db\nAEJr8tDzMKIds6sRTBT4\n-----END CERTIFICATE-----") + localHostCert := "-----BEGIN CERTIFICATE-----\\nMIIDOTCCAiGgAwIBAgIQSRJrEpBGFc7tNb1fb5pKFzANBgkqhkiG9w0BAQsFADAS\\nMRAwDgYDVQQKEwdBY21lIENvMCAXDTcwMDEwMTAwMDAwMFoYDzIwODQwMTI5MTYw\\nMDAwWjASMRAwDgYDVQQKEwdBY21lIENvMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A\\nMIIBCgKCAQEA6Gba5tHV1dAKouAaXO3/ebDUU4rvwCUg/CNaJ2PT5xLD4N1Vcb8r\\nbFSW2HXKq+MPfVdwIKR/1DczEoAGf/JWQTW7EgzlXrCd3rlajEX2D73faWJekD0U\\naUgz5vtrTXZ90BQL7WvRICd7FlEZ6FPOcPlumiyNmzUqtwGhO+9ad1W5BqJaRI6P\\nYfouNkwR6Na4TzSj5BrqUfP0FwDizKSJ0XXmh8g8G9mtwxOSN3Ru1QFc61Xyeluk\\nPOGKBV/q6RBNklTNe0gI8usUMlYyoC7ytppNMW7X2vodAelSu25jgx2anj9fDVZu\\nh7AXF5+4nJS4AAt0n1lNY7nGSsdZas8PbQIDAQABo4GIMIGFMA4GA1UdDwEB/wQE\\nAwICpDATBgNVHSUEDDAKBggrBgEFBQcDATAPBgNVHRMBAf8EBTADAQH/MB0GA1Ud\\nDgQWBBStsdjh3/JCXXYlQryOrL4Sh7BW5TAuBgNVHREEJzAlggtleGFtcGxlLmNv\\nbYcEfwAAAYcQAAAAAAAAAAAAAAAAAAAAATANBgkqhkiG9w0BAQsFAAOCAQEAxWGI\\n5NhpF3nwwy/4yB4i/CwwSpLrWUa70NyhvprUBC50PxiXav1TeDzwzLx/o5HyNwsv\\ncxv3HdkLW59i/0SlJSrNnWdfZ19oTcS+6PtLoVyISgtyN6DpkKpdG1cOkW3Cy2P2\\n+tK/tKHRP1Y/Ra0RiDpOAmqn0gCOFGz8+lqDIor/T7MTpibL3IxqWfPrvfVRHL3B\\ngrw/ZQTTIVjjh4JBSW3WyWgNo/ikC1lrVxzl4iPUGptxT36Cr7Zk2Bsg0XqwbOvK\\n5d+NTDREkSnUbie4GeutujmX3Dsx88UiV6UY/4lHJa6I5leHUNOHahRbpbWeOfs/\\nWkBKOclmOV2xlTVuPw==\\n-----END CERTIFICATE-----" + clientAuth := "-----BEGIN CERTIFICATE-----\\nMIIBVzCB/6ADAgECAgkAg/SeNG3XqB0wCgYIKoZIzj0EAwIwEDEOMAwGA1UEAwwF\\nTXkgQ0EwIBcNMjIwMTIxMTUxMjM0WhgPMzAyMTA1MjQxNTEyMzRaMBExDzANBgNV\\nBAMMBmNsaWVudDBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABKM7OJQMYG4KLtDA\\ngZ8zOg2PimHMmQnjD2HtI4cSwIUJJnvHWLowbFe9fk6XeP9b3dK1ImUI++/EZdVr\\nABAcngejPzA9MA4GA1UdDwEB/wQEAwIBBjAMBgNVHRMBAf8EAjAAMB0GA1UdDgQW\\nBBSttJe1mcPEnBOZ6wvKPG4zL0m1CzAKBggqhkjOPQQDAgNHADBEAiBPSLgKA/r9\\nu/FW6W+oy6Odm1kdNMGCI472iTn545GwJgIgb3UQPOUTOj0IN4JLJYfmYyXviqsy\\nzk9eWNHFXDA9U6U=\\n-----END CERTIFICATE-----" + clientAuthKey := testingKey("-----BEGIN EC TESTING KEY-----\\nMHcCAQEEINDaMGkOT3thu1A0LfLJr3Jd011/aEG6OArmEQaujwgpoAoGCCqGSM49\\nAwEHoUQDQgAEozs4lAxgbgou0MCBnzM6DY+KYcyZCeMPYe0jhxLAhQkme8dYujBs\\nV71+Tpd4/1vd0rUiZQj778Rl1WsAEByeBw==\\n-----END EC TESTING KEY-----") + clientAuthKeyEncrypted := testingKey("-----BEGIN EC TESTING KEY-----\\nProc-Type: 4,ENCRYPTED\\nDEK-Info: AES-256-CBC,3E311E9B602231BFB5C752071EE7D652\\n\\nsAKeqbacug0v4ruE1A0CACwGVEGBQVOl1CiGVp5RsxgNZKXzMS6EsTTNLw378coF\\nKXbF+he05HIuzToOz2ANLXov1iCrVpotKVB4l2obTQvg+5VET902ky99Mc9Us7jd\\nUwW8LpXlSlhcNWuUfK6wyosL42TbcIxjqZWaESW+6ww=\\n-----END EC TESTING KEY-----") + clientAuthBad := "-----BEGIN CERTIFICATE-----\\nMIIB2TCCAX6gAwIBAgIUJIZKiR78AH2ioZ+Jae/sElgH85kwCgYIKoZIzj0EAwIw\\nEDEOMAwGA1UEAwwFTXkgQ0EwHhcNMjMwNzA3MTAyNjQ2WhcNMjQwNzA2MTAyNjQ2\\nWjARMQ8wDQYDVQQDDAZjbGllbnQwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAASj\\nOziUDGBuCi7QwIGfMzoNj4phzJkJ4w9h7SOHEsCFCSZ7x1i6MGxXvX5Ol3j/W93S\\ntSJlCPvvxGXVawAQHJ4Ho4G0MIGxMAkGA1UdEwQCMAAwEQYJYIZIAYb4QgEBBAQD\\nAgWgMCwGCWCGSAGG+EIBDQQfFh1Mb2NhbCBUZXN0IENsaWVudCBDZXJ0aWZpY2F0\\nZTAdBgNVHQ4EFgQUrbSXtZnDxJwTmesLyjxuMy9JtQswHwYDVR0jBBgwFoAUpLpA\\nQPJYBb7wSQGCrKElEfj1+9YwDgYDVR0PAQH/BAQDAgXgMBMGA1UdJQQMMAoGCCsG\\nAQUFBwMEMAoGCCqGSM49BAMCA0kAMEYCIQDcHrzug3V3WvUU+tEKhG1C4cPG5rPJ\\n/y3oOoM0roOnsgIhAP23UmiC6Qdgj+MOhXWSaNt3exWvlxdKmLm2edkxaTs+\\n-----END CERTIFICATE-----" + trivialKeyPassword := "abc123" - clientCertNoAuth := "-----BEGIN CERTIFICATE-----\\nMIIB2TCCAX6gAwIBAgIUJIZKiR78AH2ioZ+Jae/sElgH85kwCgYIKoZIzj0EAwIw\\nEDEOMAwGA1UEAwwFTXkgQ0EwHhcNMjMwNzA3MTAyNjQ2WhcNMjQwNzA2MTAyNjQ2\\nWjARMQ8wDQYDVQQDDAZjbGllbnQwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAASj\\nOziUDGBuCi7QwIGfMzoNj4phzJkJ4w9h7SOHEsCFCSZ7x1i6MGxXvX5Ol3j/W93S\\ntSJlCPvvxGXVawAQHJ4Ho4G0MIGxMAkGA1UdEwQCMAAwEQYJYIZIAYb4QgEBBAQD\\nAgWgMCwGCWCGSAGG+EIBDQQfFh1Mb2NhbCBUZXN0IENsaWVudCBDZXJ0aWZpY2F0\\nZTAdBgNVHQ4EFgQUrbSXtZnDxJwTmesLyjxuMy9JtQswHwYDVR0jBBgwFoAUpLpA\\nQPJYBb7wSQGCrKElEfj1+9YwDgYDVR0PAQH/BAQDAgXgMBMGA1UdJQQMMAoGCCsG\\nAQUFBwMEMAoGCCqGSM49BAMCA0kAMEYCIQDcHrzug3V3WvUU+tEKhG1C4cPG5rPJ\\n/y3oOoM0roOnsgIhAP23UmiC6Qdgj+MOhXWSaNt3exWvlxdKmLm2edkxaTs+\\n-----END CERTIFICATE-----" type testState struct { *modulestest.Runtime @@ -962,25 +962,31 @@ func TestClient_TlsParameters(t *testing.T) { name: "ConnectTls", setup: func(tb *httpmultibin.HTTPMultiBin) { clientCAPool := x509.NewCertPool() - clientCAPool.AppendCertsFromPEM(clientCACert) + clientCAPool.AppendCertsFromPEM(clientAuthCA) tb.ServerHTTP2.TLS.ClientAuth = tls.RequireAndVerifyClientCert tb.ServerHTTP2.TLS.ClientCAs = clientCAPool }, initString: codeBlock{code: "var client = new grpc.Client();"}, - vuString: codeBlock{code: fmt.Sprintf(`client.connect("GRPCBIN_ADDR", { tls: { cacerts: "%s", cert: "%s", key: "%s" }});`, localhostCert, clientCert, clientKey)}, + vuString: codeBlock{code: fmt.Sprintf(`client.connect("GRPCBIN_ADDR", { tls: { cacerts: "%s", cert: "%s", key: "%s" }});`, localHostCert, clientAuth, clientAuthKey)}, }, { - name: "ConnectTlsEncryptedKey", + name: "ConnectTlsEncryptedKey", + setup: func(tb *httpmultibin.HTTPMultiBin) { + clientCAPool := x509.NewCertPool() + clientCAPool.AppendCertsFromPEM(clientAuthCA) + tb.ServerHTTP2.TLS.ClientAuth = tls.RequireAndVerifyClientCert + tb.ServerHTTP2.TLS.ClientCAs = clientCAPool + }, initString: codeBlock{code: "var client = new grpc.Client();"}, - vuString: codeBlock{code: fmt.Sprintf(`client.connect("GRPCBIN_ADDR", { tls: { cacerts: ["%s"], cert: "%[1]s", key: "%s", password: "%s" }});`, localhostCert, localhostEncryptedKey, trivialKeyPassword)}, + vuString: codeBlock{code: fmt.Sprintf(`client.connect("GRPCBIN_ADDR", { tls: { cacerts: ["%s"], cert: "%s", key: "%s", password: "%s" }});`, localHostCert, clientAuth, clientAuthKeyEncrypted, trivialKeyPassword)}, }, { name: "ConnectTlsEncryptedKeyDecryptionFailed", initString: codeBlock{code: "var client = new grpc.Client();"}, vuString: codeBlock{ code: fmt.Sprintf(`client.connect("GRPCBIN_ADDR", { timeout: '5s', tls: { cert: "%s", key: "%s", password: "abc321" }});`, - clientCert, - clientEncryptedKey, + clientAuth, + clientAuthKeyEncrypted, ), err: "x509: decryption password incorrect", }, @@ -989,16 +995,16 @@ func TestClient_TlsParameters(t *testing.T) { name: "ConnectTlsClientCertNoClientAuth", setup: func(tb *httpmultibin.HTTPMultiBin) { clientCAPool := x509.NewCertPool() - clientCAPool.AppendCertsFromPEM(clientCACert) + clientCAPool.AppendCertsFromPEM(clientAuthCA) tb.ServerHTTP2.TLS.ClientAuth = tls.RequireAndVerifyClientCert tb.ServerHTTP2.TLS.ClientCAs = clientCAPool }, initString: codeBlock{code: `var client = new grpc.Client();`}, vuString: codeBlock{ code: fmt.Sprintf(`client.connect("GRPCBIN_ADDR", { timeout: '5s', tls: { cacerts: ["%s"], cert: "%s", key: "%s" }});`, - localhostCert, - clientCertNoAuth, - clientKey), + localHostCert, + clientAuthBad, + clientAuthKey), err: "remote error: tls: bad certificate", }, }, @@ -1006,18 +1012,19 @@ func TestClient_TlsParameters(t *testing.T) { name: "ConnectTlsClientCertWithPasswordNoClientAuth", setup: func(tb *httpmultibin.HTTPMultiBin) { clientCAPool := x509.NewCertPool() - clientCAPool.AppendCertsFromPEM(clientCACert) + clientCAPool.AppendCertsFromPEM(clientAuthCA) tb.ServerHTTP2.TLS.ClientAuth = tls.RequireAndVerifyClientCert tb.ServerHTTP2.TLS.ClientCAs = clientCAPool }, initString: codeBlock{code: `var client = new grpc.Client();`}, vuString: codeBlock{ code: fmt.Sprintf(` - client.connect("GRPCBIN_ADDR", { timeout: '5s', tls: { cacerts: ["%s"], cert: "%s", key: "%s", password: "abc123" }}); + client.connect("GRPCBIN_ADDR", { timeout: '5s', tls: { cacerts: ["%s"], cert: "%s", key: "%s", password: "%s" }}); `, - localhostCert, - clientCertNoAuth, - clientEncryptedKey), + localHostCert, + clientAuthBad, + clientAuthKeyEncrypted, + trivialKeyPassword), err: "remote error: tls: bad certificate", }, }, @@ -1025,7 +1032,7 @@ func TestClient_TlsParameters(t *testing.T) { name: "ConnectTlsInvokeSuccess", setup: func(tb *httpmultibin.HTTPMultiBin) { clientCAPool := x509.NewCertPool() - clientCAPool.AppendCertsFromPEM(clientCACert) + clientCAPool.AppendCertsFromPEM(clientAuthCA) tb.ServerHTTP2.TLS.ClientAuth = tls.RequireAndVerifyClientCert tb.ServerHTTP2.TLS.ClientCAs = clientCAPool tb.GRPCStub.EmptyCallFunc = func(context.Context, *grpc_testing.Empty) (*grpc_testing.Empty, error) { @@ -1042,9 +1049,9 @@ func TestClient_TlsParameters(t *testing.T) { if (resp.status !== grpc.StatusOK) { throw new Error("unexpected error: " + JSON.stringify(resp.error) + "or status: " + resp.status) }`, - localhostCert, - clientCert, - clientKey), + localHostCert, + clientAuth, + clientAuthKey), }, }, } From 2d4ce159b156215bce678bf3bb81d180fefc8a08 Mon Sep 17 00:00:00 2001 From: Chris Moran Date: Mon, 10 Jul 2023 05:46:24 -0400 Subject: [PATCH 17/24] Remove timeout from failing test Unsure why the test passes locally but fails in CI --- js/modules/k6/grpc/client_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/modules/k6/grpc/client_test.go b/js/modules/k6/grpc/client_test.go index 91b53810c72..1fc48948773 100644 --- a/js/modules/k6/grpc/client_test.go +++ b/js/modules/k6/grpc/client_test.go @@ -1019,7 +1019,7 @@ func TestClient_TlsParameters(t *testing.T) { initString: codeBlock{code: `var client = new grpc.Client();`}, vuString: codeBlock{ code: fmt.Sprintf(` - client.connect("GRPCBIN_ADDR", { timeout: '5s', tls: { cacerts: ["%s"], cert: "%s", key: "%s", password: "%s" }}); + client.connect("GRPCBIN_ADDR", { tls: { cacerts: ["%s"], cert: "%s", key: "%s", password: "%s" }}); `, localHostCert, clientAuthBad, From f71d3e0b18d2fc18e6bebae710c2b8a61356883d Mon Sep 17 00:00:00 2001 From: Chris Moran Date: Tue, 11 Jul 2023 05:40:20 -0400 Subject: [PATCH 18/24] Removed timeout on failing CI test --- js/modules/k6/grpc/client_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/modules/k6/grpc/client_test.go b/js/modules/k6/grpc/client_test.go index 1fc48948773..a2380ef770b 100644 --- a/js/modules/k6/grpc/client_test.go +++ b/js/modules/k6/grpc/client_test.go @@ -1001,7 +1001,7 @@ func TestClient_TlsParameters(t *testing.T) { }, initString: codeBlock{code: `var client = new grpc.Client();`}, vuString: codeBlock{ - code: fmt.Sprintf(`client.connect("GRPCBIN_ADDR", { timeout: '5s', tls: { cacerts: ["%s"], cert: "%s", key: "%s" }});`, + code: fmt.Sprintf(`client.connect("GRPCBIN_ADDR", { tls: { cacerts: ["%s"], cert: "%s", key: "%s" }});`, localHostCert, clientAuthBad, clientAuthKey), From c35a3b1ba2106250c57f4a9f71742ba849be6bd6 Mon Sep 17 00:00:00 2001 From: Chris Moran <116747924+chrismoran-mica@users.noreply.github.com> Date: Wed, 12 Jul 2023 10:11:44 -0400 Subject: [PATCH 19/24] Update js/modules/k6/grpc/client.go Co-authored-by: Oleg Bespalov --- js/modules/k6/grpc/client.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/modules/k6/grpc/client.go b/js/modules/k6/grpc/client.go index 3e91984063e..79111024b3a 100644 --- a/js/modules/k6/grpc/client.go +++ b/js/modules/k6/grpc/client.go @@ -115,7 +115,7 @@ func (c *Client) LoadProtoset(protosetPath string) ([]MethodInfo, error) { func decryptPrivateKey(key, password []byte) ([]byte, error) { block, _ := pem.Decode(key) if block == nil { - return nil, fmt.Errorf("failed to decode PEM key") + return nil, errors.New("failed to decode PEM key") } blockType := block.Type From 954e982b7eee7f6975461f7ea3a0e1ffdefb9afe Mon Sep 17 00:00:00 2001 From: Chris Moran <116747924+chrismoran-mica@users.noreply.github.com> Date: Wed, 12 Jul 2023 10:11:52 -0400 Subject: [PATCH 20/24] Update js/modules/k6/grpc/client.go Co-authored-by: Oleg Bespalov --- js/modules/k6/grpc/client.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/modules/k6/grpc/client.go b/js/modules/k6/grpc/client.go index 79111024b3a..7d536c5d6a0 100644 --- a/js/modules/k6/grpc/client.go +++ b/js/modules/k6/grpc/client.go @@ -120,7 +120,7 @@ func decryptPrivateKey(key, password []byte) ([]byte, error) { blockType := block.Type if blockType == "ENCRYPTED PRIVATE KEY" { - return nil, fmt.Errorf("encrypted pkcs8 formatted key is not supported") + return nil, errors.New("encrypted pkcs8 formatted key is not supported") } /* Even though `DecryptPEMBlock` has been deprecated since 1.16.x it is still From 05132c3485b7f70846ae5b6bc50ff9c7a2f3ca9d Mon Sep 17 00:00:00 2001 From: Chris Moran <116747924+chrismoran-mica@users.noreply.github.com> Date: Wed, 12 Jul 2023 10:11:59 -0400 Subject: [PATCH 21/24] Update js/modules/k6/grpc/client.go Co-authored-by: Oleg Bespalov --- js/modules/k6/grpc/client.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/modules/k6/grpc/client.go b/js/modules/k6/grpc/client.go index 7d536c5d6a0..4882d1cc123 100644 --- a/js/modules/k6/grpc/client.go +++ b/js/modules/k6/grpc/client.go @@ -555,7 +555,7 @@ func (c *Client) parseConnectParams(raw map[string]interface{}) (connectParams, for _, cacertsArrayEntry := range cacertsArray { if _, ok = cacertsArrayEntry.(string); !ok { return params, fmt.Errorf("invalid tls cacerts value: '%#v',"+ - " it needs to be a string or string[] of PEM formatted strings", v) + " it needs to be a string or an array of PEM formatted strings", v) } } } else if _, ok = cacerts.(string); !ok { From e0a13c749f071efbb7bcacfb40a0ed422109c3f7 Mon Sep 17 00:00:00 2001 From: Chris Moran <116747924+chrismoran-mica@users.noreply.github.com> Date: Wed, 12 Jul 2023 10:12:04 -0400 Subject: [PATCH 22/24] Update js/modules/k6/grpc/client.go Co-authored-by: Oleg Bespalov --- js/modules/k6/grpc/client.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/modules/k6/grpc/client.go b/js/modules/k6/grpc/client.go index 4882d1cc123..6a4ab02f949 100644 --- a/js/modules/k6/grpc/client.go +++ b/js/modules/k6/grpc/client.go @@ -560,7 +560,7 @@ func (c *Client) parseConnectParams(raw map[string]interface{}) (connectParams, } } else if _, ok = cacerts.(string); !ok { return params, fmt.Errorf("invalid tls cacerts value: '%#v',"+ - " it needs to be a string or string[] of PEM formatted strings", v) + " it needs to be a string or an array of PEM formatted strings", v) } } default: From 692387d3ee1994093c2b04009854de4d8f1db53a Mon Sep 17 00:00:00 2001 From: Chris Moran <116747924+chrismoran-mica@users.noreply.github.com> Date: Wed, 12 Jul 2023 10:12:40 -0400 Subject: [PATCH 23/24] Update js/modules/k6/grpc/client.go Co-authored-by: Mihail Stoykov <312246+mstoykov@users.noreply.github.com> --- js/modules/k6/grpc/client.go | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/js/modules/k6/grpc/client.go b/js/modules/k6/grpc/client.go index 6a4ab02f949..17249fa9988 100644 --- a/js/modules/k6/grpc/client.go +++ b/js/modules/k6/grpc/client.go @@ -222,13 +222,11 @@ func (c *Client) Connect(addr string, params map[string]interface{}) (bool, erro var tcred credentials.TransportCredentials if !p.IsPlaintext { - var tlsCfg *tls.Config + tlsCfg := state.TLSConfig.Clone() if len(p.TLS) > 0 { - if tlsCfg, err = buildTLSConfigFromMap(state.TLSConfig.Clone(), p.TLS); err != nil { + if tlsCfg, err = buildTLSConfigFromMap(tlsCfg, p.TLS); err != nil { return false, err } - } else { - tlsCfg = state.TLSConfig.Clone() } tlsCfg.NextProtos = []string{"h2"} From af7770cd89eda45f6f54a7131b5fc1225f3b5c51 Mon Sep 17 00:00:00 2001 From: Chris Moran Date: Wed, 12 Jul 2023 11:20:30 -0400 Subject: [PATCH 24/24] Fixed the test cased tied to the PR requested change that was committed --- js/modules/k6/grpc/client_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/modules/k6/grpc/client_test.go b/js/modules/k6/grpc/client_test.go index a2380ef770b..b0c37137bb3 100644 --- a/js/modules/k6/grpc/client_test.go +++ b/js/modules/k6/grpc/client_test.go @@ -955,7 +955,7 @@ func TestClient_TlsParameters(t *testing.T) { initString: codeBlock{code: "var client = new grpc.Client();"}, vuString: codeBlock{ code: `client.connect("GRPCBIN_ADDR", { tls: { cert: "", key: "", cacerts: 0 }});`, - err: `invalid grpc.connect() parameters: invalid tls cacerts value: 'map[string]interface {}{"cacerts":0, "cert":"", "key":""}', it needs to be a string or string[] of PEM formatted strings`, + err: `invalid grpc.connect() parameters: invalid tls cacerts value: 'map[string]interface {}{"cacerts":0, "cert":"", "key":""}', it needs to be a string or an array of PEM formatted strings`, }, }, {