Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support for per-vu TLS configuration (grpc.Client.connect only) #3159

Conversation

chrismoran-mica
Copy link
Contributor

@chrismoran-mica chrismoran-mica commented Jul 2, 2023

What? and Why?

You can 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 tls key that loosely resembles the TLSAuth struct. This will create allow 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.

*Note: the grpc.Client, once connected will utilize the param tlsconfig regardless of the VU that uses the grpc.Client. In order to truly have per-vu tls configuration steps must be taken on the test to ensure each VU uses the grpc.Client with its custom tlsconfig.
For example, in my use case I use multiple VUs to connect to a configurable number of endpoints. Each of these endpoints are mTLS secured with potentially different CA-signed certificates. Each VU will use the grpc.Client with the tlsconfig targeting the relevant endpoint. As an implementation of this may not be obvious, I've included a trivial example below. Allowing different VUs to use different endpoint values I assume is trivial (or it can be considered an exercise for the reader).

HINT: one way would be to load aSharedArray (or SharedArrays) with shards of data. Each index in the SharedArray(s) would be used by a VU. So if you have 10 sets of data and run 10 VUs, you can have each VU pull from their own index in the SharedArrays.

// init phase (1)
const params = {
  "grpcbin.test.notk6.io:9001": {
    plaintext: false,
    tls: {
      cacerts: [open("cacerts0.pem")],
      cert: open("cert0.pem"),
      key: open("key0.pem"),
    }, // password omitted to demonstrate 'optional password'
  },
  "grpcbin.test.fakek6.io:9001": {
    plaintext: false,
    tls: {
      cacerts: open("cacerts1.pem"),
      cert: open("cert1.pem"),
      key: open("key1.pem"),
      password: "cert1-passphrase",
    }, // cacerts as a 'string' to demonstrate string|string[] typing of cacerts
  },
};
const clients = {
  "grpcbin.test.notk6.io:9001": new grpc.Client(),
  "grpcbin.test.fakek6.io:9001": new grpc.Client(),
};
...

// k6 - VU code phase (3)
if (__ITER === 0) {
  clients["grpcbin.test.notk6.io:9001"]
    .connect("grpcbin.test.notk6.io:9001", params["grpcbin.test.notk6.io:9001"]);
  clients["grpcbin.test.notk6.io:9001"]
    .connect("grpcbin.test.fakek6.io:9001", params["grpcbin.test.fakek6.io:9001"]);
}
...

Usage:

// VU 1 - code phase (3)
const hostPort = "grpcbin.test.notk6.io:9001";
const client = clients[hostPort];

const data = { greeting: 'Bert' };
const response = client.invoke('hello.HelloService/SayHello', data);

check(response, {
  'status is OK': (r) => r && r.status === grpc.StatusOK,
});

console.log(JSON.stringify(response.message));
...
// VU 2 - code phase (3)
const hostPort = "grpcbin.test.fakek6.io:9001";
const client = clients[hostPort];

const data = { greeting: 'Ernie' };
const response = client.invoke('hello.HelloService/SayHello', data);

check(response, {
  'status is OK': (r) => r && r.status === grpc.StatusOK,
});

console.log(JSON.stringify(response.message));

Checklist

  • I have performed a self-review of my code.
  • I have added tests for my changes.
  • I have updated tests to consider my changes.
  • I have run linter locally (make lint) and all checks pass.
  • I have run linter locally (make ci-like-lint) and all checks pass.
  • I have run tests locally (make tests) and all tests pass.
  • I have commented on my code, particularly in hard-to-understand areas.

Related PR(s)/Issue(s)

This PR does not close an issue but it does reference at least one conversation about per-vu mTLS: https://community.k6.io/t/grpc-mtls-per-vu-iteration/1868

…nd 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);
```
@CLAassistant
Copy link

CLAassistant commented Jul 2, 2023

CLA assistant check
All committers have signed the CLA.

@github-actions github-actions bot requested review from imiric and mstoykov July 2, 2023 12:37
imiric
imiric previously requested changes Jul 4, 2023
Copy link
Contributor

@imiric imiric left a comment

Choose a reason for hiding this comment

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

Hi there, thanks a lot for this PR! 🙇

I think it makes sense to do this for the gRPC client, but I'm not so sure about the global tlsAuth change. And I'd like to see some gRPC tests for this.

js/modules/k6/grpc/client.go Show resolved Hide resolved
lib/options.go Outdated Show resolved Hide resolved
js/modules/k6/grpc/client.go Outdated Show resolved Hide resolved
js/modules/k6/grpc/client.go Outdated Show resolved Hide resolved
@chrismoran-mica chrismoran-mica changed the title Support for per-vu TLS configuration (grpc.Client.connect only) and custom (self-signed) RootCAs Support for per-vu TLS configuration (grpc.Client.connect only) Jul 4, 2023
Added tests around tls password and encrypted certificate key
@chrismoran-mica
Copy link
Contributor Author

chrismoran-mica commented Jul 4, 2023

Thank you for your feedback @imiric.

I've addressed all of the feedback requests and added some (admittedly not 100% coverage) test cases for the new connectParams tls option. Let me know what I've missed that must/should be included before merge.

(I've also modified the PR description and title to reflect the narrower scope of this).

Please let me know what more I can do to facilitate this PR. I noticed the documentation-needed tag. Shall I create a new PR with the relevant updated documentation?

@mstoykov mstoykov added this to the v0.46.0 milestone Jul 5, 2023
@mstoykov mstoykov added documentation-needed A PR which will need a separate PR for documentation area: grpc labels Jul 5, 2023
…alhostKey and localhostEncryptedKey. (Used the same approach `net/http/internal/testcert/testcert.go` uses)
Copy link
Collaborator

@mstoykov mstoykov left a comment

Choose a reason for hiding this comment

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

LGTM in general! Thanks for the contribution 🙇

I have left a bunch of change requests and some questions.

My two concerns (which are not inline) are:

  1. Organizational - we now have an experimental grpc module where we have some changes. This change will now need to be transfered there. While we might've prefered it the other way around. cc @olegbespalov
  2. API one - k6 has problems with big files arguably with the handling of any files to be honest, just big ones make it worse. This particular case seems okay to me. Especially if users figure out how to load the key/cert once . Whihc arguably is doable with a SharedArray with element per each cert+key+password. My other problem is that currently you went with the key and cert being strings instead of binary data. Which I guess is based on what formats golang supports? I do ask the same question inline.

And both of those questions could've been answered if we had an issue to discuss this before hand.

p.s. Your example in the description shows "cert" (and all the other big options) as what looks like file names. But they are actually the contents of those. I would prefer to have the example either just use open("./cert.pem") or be the string (with a lot of ...) that is the actual contents. As in this way it is confusing for both me as reviewer and likely anyone else who in the future tries to figure out what was added here.

js/modules/k6/grpc/client.go Outdated Show resolved Hide resolved
js/modules/k6/grpc/client.go Show resolved Hide resolved
js/modules/k6/grpc/client.go Show resolved Hide resolved
js/modules/k6/grpc/client.go Outdated Show resolved Hide resolved
js/modules/k6/grpc/client.go Outdated Show resolved Hide resolved
js/modules/k6/grpc/client.go Outdated Show resolved Hide resolved
js/modules/k6/grpc/client.go Outdated Show resolved Hide resolved
js/modules/k6/grpc/client_test.go Outdated Show resolved Hide resolved
chrismoran-mica and others added 4 commits July 6, 2023 07:12
Nice catch. Thank you.

Co-authored-by: Mihail Stoykov <312246+mstoykov@users.noreply.github.com>
Nice catch. Thank you.

Co-authored-by: Mihail Stoykov <312246+mstoykov@users.noreply.github.com>
Added positive and negative test cases for grpc client connect with tls parameter
chrismoran-mica and others added 3 commits July 7, 2023 08:23
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>
…ith-rootcas' into feature/per-vu-grpc-tls-config-with-rootcas
@chrismoran-mica
Copy link
Contributor Author

@mstoykov

I've updated my trivial examples with open statements to address your concern mentioned above.

@chrismoran-mica
Copy link
Contributor Author

I believe I've addressed all of the feedback so far. Please let me know what else I can do to prepare this for v0.46.0.

@codebien codebien requested review from codebien and removed request for imiric July 10, 2023 08:06
Chris Moran added 2 commits July 10, 2023 05:46
Unsure why the test passes locally but fails in CI
mstoykov
mstoykov previously approved these changes Jul 12, 2023
Copy link
Collaborator

@mstoykov mstoykov left a comment

Choose a reason for hiding this comment

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

LGTM! thanks for the contribution.

js/modules/k6/grpc/client.go Outdated Show resolved Hide resolved
olegbespalov
olegbespalov previously approved these changes Jul 12, 2023
Copy link
Collaborator

@olegbespalov olegbespalov left a comment

Choose a reason for hiding this comment

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

LGTM, left a few non-blocking suggestions 👍

Thanks for the contribution!

js/modules/k6/grpc/client.go Outdated Show resolved Hide resolved
js/modules/k6/grpc/client.go Outdated Show resolved Hide resolved
js/modules/k6/grpc/client.go Outdated Show resolved Hide resolved
js/modules/k6/grpc/client.go Outdated Show resolved Hide resolved
Co-authored-by: Oleg Bespalov <olegbespalov@gmail.com>
chrismoran-mica and others added 4 commits July 12, 2023 10:11
Co-authored-by: Oleg Bespalov <olegbespalov@gmail.com>
Co-authored-by: Oleg Bespalov <olegbespalov@gmail.com>
Co-authored-by: Oleg Bespalov <olegbespalov@gmail.com>
Co-authored-by: Mihail Stoykov <312246+mstoykov@users.noreply.github.com>
olegbespalov
olegbespalov previously approved these changes Jul 12, 2023
mstoykov
mstoykov previously approved these changes Jul 12, 2023
@mstoykov mstoykov dismissed imiric’s stale review July 12, 2023 14:36

The global tlsAuth was addressed

@chrismoran-mica
Copy link
Contributor Author

I approved the requested changes but unfortunately one of them broke a couple tests :)

I've fixed them I believe. Sorry for the ping-pong!

@mstoykov mstoykov merged commit d567ac2 into grafana:master Jul 12, 2023
22 checks passed
@mstoykov
Copy link
Collaborator

For future reference while squashing the changes I did delete the mention of the global tlsAuth.

But unfortunately did not fix the example to use the actual contents of a pem file and instead it still has the pem file name 🤦 .

This likely doesn't really matter as both the current PR body and any documentation will have this correct.

@chrismoran-mica
Copy link
Contributor Author

chrismoran-mica commented Jul 12, 2023 via email

@mstoykov
Copy link
Collaborator

@chrismoran-mica I should've been more specific. This wasn't your fault.
When you squash on github it presents you with the commit messages not the PR description.

Arguably you could've changed the commit message and force pushed, but I would probably also forget to do it or not even think of it.

I should've just been more careful and arguably just written a completely knew message when squashing. It is my fault

olegbespalov pushed a commit to grafana/xk6-grpc that referenced this pull request Jul 17, 2023
* This is a port of the PR changes found here:
grafana/k6#3159

Note: The bad auth/bad certificate tests appear to be very inconsistent. In grafana/k6 I had to remove the timeout for them to pass in CI. Removing the timeout in grafana/xk6-grpc caused the tests to fail locally. This may need to be investigated or a bug raised to determine if k6 is not raising grpc connection errors on bad authentication until context deadline.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area: grpc documentation-needed A PR which will need a separate PR for documentation
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

6 participants