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

Refactor Client constructor options, add (m)TLS support #17

Merged
merged 14 commits into from
Nov 2, 2023

Conversation

imiric
Copy link
Contributor

@imiric imiric commented Jul 7, 2023

This is a fix/feature implementation for #13.

It refactors the Client constructor to accept options that are better aligned with the Node Redis API, to increase familiarity with an established JS library, and to allow specifying (m)TLS options, as proposed here. This means that these are breaking changes for users of the previous API.

See the updated README and the tests for usage examples.

If you want to test this manually with a real Redis TLS server, see this gist for instructions.

What's pending:

  • There's a strange issue when using mTLS and the original example script, that causes Redis to abort with ERR Protocol error: unauthenticated multibulk length.
    I tracked it down to this issue, and this comment mentions this being some MSET limitation. We use regular SET in the script, but maybe this is a general limitation of some kind(?). I'm not sure, but we'll need to look into it.
    According to this other comment, there's a nasty fix for this... 🙈
    UPDATE 2023-08-10: I haven't been able to reproduce this again, but we should be aware that it might be an issue to look into later.
  • General cleanup and DRYing.
  • Fix existing tests, and adding more.
  • Address all the TODOs, and feedback from this PR.
  • Maybe split this PR into separate refactor and TLS changes PRs, but I wanted to leave them together as a draft for now.
    UPDATE 2023-08-10: I decided to leave it as a single PR, since the changes are closely related.

@imiric imiric requested a review from oleiade July 7, 2023 16:00
@imiric imiric marked this pull request as draft July 7, 2023 16:01
redis/options.go Outdated Show resolved Hide resolved
@imiric imiric force-pushed the feat/13-tls-refactor branch 2 times, most recently from 2009287 to 2119bc6 Compare August 10, 2023 15:06
@imiric imiric changed the title WIP Refactor Client constructor options, add TLS support Refactor Client constructor options, add (m)TLS support Aug 10, 2023
@imiric imiric marked this pull request as ready for review August 10, 2023 15:16
@imiric
Copy link
Contributor Author

imiric commented Aug 10, 2023

@oleiade This is now ready for a proper review. ☺️

The commit log is a bit messy, but it's a PITA to clean it up at this point. 😓 You can just review it as a general diff, going by file.

Please take a look and let me know.

@imiric imiric linked an issue Aug 10, 2023 that may be closed by this pull request
Comment on lines +166 to +190
{
name: "err/object/cluster_inconsistent_option",
arg: `{
cluster: {
nodes: [
{
username: 'user1',
password: 'pass',
socket: {
host: 'host1',
port: 6379,
},
},
{
username: 'user2',
password: 'pass',
socket: {
host: 'host2',
port: 6379,
},
}
]
}
}`,
expErr: `invalid options; reason: inconsistent username option: user1 != user2`,
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'm still not sure about this behavior. redis.UniversalOptions only allows a single username, password and many other values, even if more than one Addrs is specified. So the point of setConsistentOptions() is to ensure that these values are consistent across all nodes.

But I'm not sure if this is something that's also enforced by Redis, or if there can be a case where e.g. nodes within a cluster use a different username or password, or any other setting. Intuitively, I don't see why that shouldn't be allowed, in which case there would be no way for users to connect to such a cluster. But maybe this is also a Redis limitation that go-redis is compliant with, in which case this is fine.

I'm not familiar with Redis Cluster deployments, and didn't find anything specifically about this in the documentation. My hunch is that this is fine, but wanted to point it out.

redis/stub_test.go Show resolved Hide resolved
@CLAassistant
Copy link

CLAassistant commented Oct 9, 2023

CLA assistant check
All committers have signed the CLA.

@oleiade
Copy link
Member

oleiade commented Oct 12, 2023

Hey @imiric, it seems since you left you now need to sign the CLA 😓 Would you mind doing it, please? You mentioned we might have to split the PR, but if we don't I'd prefer to keep your contribution acknowledged 👍🏻

@imiric imiric requested a review from a team as a code owner October 12, 2023 08:01
@imiric imiric requested review from mstoykov and removed request for a team October 12, 2023 08:01
@imiric
Copy link
Contributor Author

imiric commented Oct 12, 2023

Hey @oleiade! This wouldn't be an issue had this PR been reviewed in a timely manner... 😜

I think the CLA here is fine:
2023-10-12-102734_987x204_scrot

But GH/CLA Assistant might be confused since I deleted the Grafana email from my account, so it can't associate the commits with my account. I think the solution is as you have done by recreating the commits as yourself, and leaving me as the author.

BTW, I don't think this PR should be split, as it would be a lot of work for no real gain. I'm happy to address any feedback, though I may be slow to respond.

Hope all is well. Cheers!

@oleiade
Copy link
Member

oleiade commented Oct 12, 2023

@imiric Touché ❤️‍🔥 ! That's totally fair. Sorry it took so long for me to get back to it 🙇🏻

I will proceed with recreating the commits myself, leaving you as an author, and keeping the branch as is then 🤝

All is well on this side of the Pyrenees, I hope it's the same for you 🖖🏻

@codebien
Copy link
Contributor

codebien commented Oct 13, 2023

This worked for me for the latest commit.

git commit --amend --author="Ivan Mirić <1009277+imiric@users.noreply.github.com>"

@oleiade You have to loop over the commits applying the command with the specific commit or use the same command as I did doing a rebase and setting all the commits with the edit stage.

@oleiade
Copy link
Member

oleiade commented Oct 16, 2023

@codebien Thanks for the heads-up, the issue I have is not with changing the email itself, but rather which email to use indeed 🙇🏻

At the moment, it is set to ivan@loadimpact.com as the author, which I assume has been deleted.

@codebien
Copy link
Contributor

but rather which email to use indeed

@oleiade you can use the mail I've posted that connects Ivan to his GitHub account. You don't need a real e-mail.

@imiric
Copy link
Contributor Author

imiric commented Oct 16, 2023

@codebien is right, and @oleiade please remove my personal emails from your comment. 🙏

@oleiade
Copy link
Member

oleiade commented Oct 17, 2023

@codebien how did you find the information as to which email to use so I know where to look next time?

@oleiade oleiade requested a review from codebien October 19, 2023 12:19
Copy link
Contributor

@codebien codebien left a comment

Choose a reason for hiding this comment

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

LGTM, I left some nitpicks. The unique point I have is regarding the API that seems to have some overlap between the possible ways of providing the same configuration where I would prefer a single way for them, but at this point, I think it's more important to merge this and start collecting feedback.

README.md Outdated Show resolved Hide resolved
redis/client.go Outdated Show resolved Hide resolved
oleiade and others added 2 commits October 20, 2023 14:04
Co-authored-by: Ivan <2103732+codebien@users.noreply.github.com>
Co-authored-by: Ivan <2103732+codebien@users.noreply.github.com>
Copy link
Member

@oleiade oleiade left a comment

Choose a reason for hiding this comment

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

LGTM 👍🏻 🚀

Took me some time to get all the "infrastructure" I needed to test this properly, but I'm quite happy how this whole PR, and I'm keen to merge it as is. Outstanding work 🙇🏻

@oleiade
Copy link
Member

oleiade commented Oct 20, 2023

I'll start documenting the change in k6-docs PR while we wait for @mstoykov 's review.

redis/client.go Outdated
Comment on lines 1105 to 1112
k6dialer, ok := vuState.Dialer.(*netext.Dialer)
if !ok {
panic(fmt.Sprintf("expected *netext.Dialer, got: %T", vuState.Dialer))
}
tlsDialer := &tls.Dialer{
NetDialer: &k6dialer.Dialer,
Config: tlsCfg,
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Unfortunately I do think this completely circumvent the current tings netext.Dialer does:

  1. Doing the resolving of the hostnames - including over options.hosts
  2. blacklisting both hostnames and ips - which likely will be a big problem in cloud
  3. emtting bytes read/written - which in this case might be an okay thing.

Looking at tls.Dialer though - it seems not possible to put netext.Dialer there. So maybe we should wrap it the other way aroudn 🤔

Copy link
Member

Choose a reason for hiding this comment

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

Good catch indeed 👍🏻 I gave a stab at addressing this concern in 3676bef, by defining a custom dialer function which relies on k6's VU (netext.Dialer) dialer, and manually upgrades the connection to TLS (respecting the provided configuration) when TLS certificates are present.

Testing it showed that blocked hostnames were effectively taken into account.

Let me know if that addresses your point @mstoykov 🙇🏻

Copy link
Contributor

@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.

I did look more into this but IMO https://github.com/grafana/xk6-redis/pull/17/files#r1369707388 and it security implications are blockign this PR at this point.

I am okay if we can figure out a way to disable this functionality to merge it so that the PR doesn't stay open and continue working on the underlying problem in a separate PR. cc @oleiade

@oleiade
Copy link
Member

oleiade commented Oct 25, 2023

@mstoykov I'm currently looking into this indeed 👀 There's quite a bit of context involved. If I remember correctly, we wanted to do that for a specific reason (because the k6 TLS dialer is specific to HTTP), but I need to spend more time loading the context back into my brain. I will come back to this as soon as I've been able to wrap my head around it again 👍🏻

Copy link
Contributor

@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.

LGMT! I would still prefer a test around blockhostnames or something like that, but looking at the code I do expect it will work.

My only other feedback is that the current options parsing has a bunch of high level issues that do not have easy fixes:

  1. it exports a goja object instead of reading directly from it - which then requires a bunch of type casting and json marshalling and unmarshalling
  2. The above also prevents you from supporting times as both numbers and strings - which is a thing k6 generally does see Unify duration configuration values k6#1738
  3. Also because you do print the exported struct on error I guess you can hit stack overflow while processing a encrypt http request k6#644 (comment)

Fixes for all of those though in practice require that you do parse the config field by field from the base object which is arguably a thing we do in very few places.

So this is mostly a reminder this is not great then a let's fix it given the amount of options

redis/client_test.go Outdated Show resolved Hide resolved
@oleiade oleiade merged commit b5c72ea into master Nov 2, 2023
10 checks passed
@oleiade oleiade deleted the feat/13-tls-refactor branch November 2, 2023 10:34
@oleiade
Copy link
Member

oleiade commented Nov 2, 2023

@mstoykov I'll integrate your feedback in a(multiple?) issue(s) 🙇🏻

@oleiade
Copy link
Member

oleiade commented Nov 2, 2023

Opened #26 and #27 as a result of your comment @mstoykov.

Regarding your last point, and the possibility of grafana/k6#644 (comment) being a thing, could you point me to a specific place you had in mind when mentioning "you do print the exported struct on error" please? 🙇🏻

@mkosta
Copy link

mkosta commented Mar 27, 2024

Guys, sorry dont know where to report properly, but i noticed an issue in new redis client Options (k6 v50)
using smth like this

    executor: 'ramping-arrival-rate',
    exec: "measureRedisPerformance",
    startRate:  10000,
    timeUnit: '10m',
    preAllocatedVUs: 1350,
    stages: [
      { target: 10000, duration: '1m' },
      { target: 10000, duration: '60m' },
    ],    

connection string
const redisClient = new redis.Client({socket:{host:"localhost",port:6379,minIdleConns:1500,maxConnAge:10000,poolSize:20}});

if i provide any value in minIdleConns (default value not mentioned in Docs)

connections are consumed non stop, and in a split second like (ok minute) are reaching maxClients =10k

however if minIdleConns not specified, connected_clients reaches around 1353 (looks like around preallocated vus) and is properly reused, not going all the way to maxClients=10k

it seems this paramater if provided causes this mishandling of connections
thanks

@codebien
Copy link
Contributor

Hey @mkosta,
can you please open an issue reporting it?

@mkosta
Copy link

mkosta commented Mar 27, 2024

@codebien
done
#30
thanks

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Failed to connect redis with tls
6 participants