-
Notifications
You must be signed in to change notification settings - Fork 2.5k
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 authentication switch request and basic auth plugin. #1776
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm traveling, but since I am constantly gettting emails from new commits on this PR, I figured I'd at least try to take a quick look on my phone. I only spotted one obvious error, and will take a closer look as soon as I can.
Great work on this!
lib/protocol/Auth.js
Outdated
// mysql_old_password only need first 8 bytes scremble. | ||
return this.scramble323(pluginData.slice(0, 8), password); | ||
default: | ||
throw 'Unknown auth plugin: ' + pluginName; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should throw an error object, with a proper code and fatal flag so users can handle it like the other errors.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure.
Awesome, thanks @elemount ! The last question I had here is that I believe you are the one who asked about sha256_password authentication plugin support. This pull request doesn't add that, but does this pull request implement the plugin system talked about so you'd be able to code sha256_password authentication plugin support in your code? If so, there's no documentation about how one would go about this in the pull request. If not, what is the plan to add sha256_password authentication plugin (or any custom plugin users may have setup in their MySQL)? Just curious because once this lands, if adding plugin support on top of this is hard, probably going to be very plainful to land when we already know user plugins is a use-case. |
/cc @sidorares to take a look at this implementation as well for parity with mysql2. |
@dougwilson , you raise a great problem for me. In fact, I still want to add sha256_password_plugin. But with some thought, I think put the sha256_password_plugin in this pull request is not wise. The main problem is the tests. In this pull request, all the code is both in UT and integration test. But for sha256_password_plugin, it hard to use integration tests. I have a local MySQL environment to test the sha256_password_pluign, to make other one test this plugin, I try to found a MySQL docker image with openssl build support, which can fully test the sha256_password_plugin, but I failed. Now I decide to build such docker image by myself, and can deliver to other people to test it easily. But it also needs some time. Another one is the design. I see both Golang driver and Python driver PyMySQL has a pluggable auth plugin like that pull request, I think this pluggable auth plugin is a good design for that. In my plan, I want to
The pluggable auth plugin must be supported in future because I see MariaDB is planning to have another plugin https://mariadb.org/history-of-mysql-mariadb-authentication-protocols/ . And if user want to use Amazon RDS with IAM plugin, they can also just provide a class to the connection string. But I do not want to merge the client plugin auth attribute and the pluggable auth plugin in one pull request. The main problem is complex, and I also concern that you may not accept my pluggable auth plugin design. The client plugin auth attribute is the first step, but also the basic step. Mark the attribute ON firstly and then in next pull request we have a pluggable auth plugin and then sha256_password_plugin and other auth plugins, step by step. Give me your comments on my plan, or what's your plan on it? |
@elemount @dougwilson I am going to change auth plugin api to make it easier to configure especially if you need to handle multiple plugins - sidorares/node-mysql2#560 (comment) Current api documented here: https://github.com/sidorares/node-mysql2/blob/master/documentation/Authentication-Switch.md const conn = mysql.createClient({
authPlugins: {
some_custom_plugin: (data, cb) => { /* ... * /},
// mysql_native_plugin and sha256_password_plugin automaticlly merged to here unless you do something like
// mysql_native_plugin: null
// 'mysql_clear_password' not enabled by default:
mysql_clear_password: mysql.authPlugins.mysql_clear_password
},
connectAuthPluginName: 'mysql_native_password' // this would be default, but if you need to start with sha256_password_plugin for example you need to put this option
}) would be good to add AD and PAM plugins to examples but no need to bundle them with driver |
@sidorares I think this design is great. |
@elemount note that in theory there might me multiple data roundtrips between server and auth plugins, not just single helloFromServer(plugin name, hello data) => helloFromClient( client hello response data ) . Maybe there should be extra parameter? |
@sidorares , In fact, I do not think In my mind, if we describe the connection as a state machine, the pluggable auth plugin is a node which we impose to contributors and users to implement. If the auth plugin is just a node(a function), for complicated auth plugin, it must be a node of stack based state machine, which means it can pop its last push state and push its newest state. SequenceId is means that the when function is called, then it pop a id, and after it finished, it push the next id, not flexible enough, and also not usable enough. I advise you can describe how to implement complicated auth plugin in the document sample, instead of the uncompleted solution |
const createPlugin = () => {
let state = 'start';
return (data, cb) => {
if (state === 'start') {
state = 'start2';
return cb(null, 'foo');
}
if (state === 'start2') {
return cb(null, 'bar');
}
});
const conn = mysql.createClient({
authPlugins: {
foo_bar_plugin = createPlugin()
}
}) |
Or what about that, @sidorares ? function ({pluginName, pluginData, extraPluginInfo}, cb) {
if (pluginName === 'ssh-key-auth') {
getPrivateKey(function (key) {
var response = encrypt(key, pluginData);
if (extraPluginInfo == 'phase1') {
return cb(null, response, 'phase2' /*this is the new extraPluginInfo*/);
} else {
// continue handshake by sending response data
// respond with error to propagate error to connect/changeUser handlers
cb(null, response, 'end')
};
});
} else {
const err = new Error(`Unknown AuthSwitchRequest plugin name ${pluginName}`);
err.fatal = true;
cb(err);
} |
@sidorares Could you help to review the code first? |
looks good to me. Re parity with mysql2 - this pr currently does not add user-supplied pluggable auth and only implements standard mysql_native_password and mysql_old_password plugins, so no new api at the moment |
@elemount can you rebase pr please? |
@sidorares I've rebased it. |
@elemount does this PR take into account all the comments to PR #1730 you linked to? For example, one thing we talked about was adding an integration test so we can know if at some point the string[EOF] vs string[NUL] bug is fixed in MySQL, but I'm not sure I see that in this PR, but maybe I'm missing it? Can you point me to it? |
} | ||
|
||
ComChangeUserPacket.prototype.parse = function(parser) { | ||
this.command = parser.parseUnsignedNumber(1); | ||
this.user = parser.parseNullTerminatedString(); | ||
this.scrambleBuff = parser.parseLengthCodedBuffer(); | ||
this.database = parser.parseNullTerminatedString(); | ||
this.charsetNumber = parser.parseUnsignedNumber(1); | ||
this.charsetNumber = parser.parseUnsignedNumber(2); | ||
if (!parser.reachedPacketEnd()) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shouldn't this key off the fact that plugin auth is used, not if packet end was reached? For example, this will not work with CLIENT_CONNECT_ATTRS because if client auth is off but CLIENT_CONNECT_ATTRS is on, then the packet end is indeed not reached, but the rest of the packet is not related to client plugin auth.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure. Let me change the parse.
And forgive me if you answered my question, but even re-reading your responses I'm still not sure: what is the plan to add sha256_password authentication plugin (or any custom plugin users may have setup in their MySQL)? Just curious because once this lands, if adding plugin support on top of this is results in a breaking change, it may end up never landing... |
Also, forgot to note: please add some information in the README about this, for example the list of plugins supported. Also update the https://github.com/mysqljs/mysql#connection-flags section with the changes you made to the default flags. |
@dougwilson , I use another way to avoid the string[EOF] and string[NUL] bug problem. In protocol level, the AuthSwitch packet contains In real world level, the But fortunately we do not need to handle this mismatch. How? Also by the protocol. Thus I avoid the real world mismatch by following the MySQL client protocol. |
Right, I'm fine with that slice, but what I'm talking about is the underlying packet read. Because the protocol and the MySQL server implementation disagree, this is an important aspect to make sure we have an actual integration test, and not simply a unit test. This is important when we test against other implementations like MarisDB, Azure, and any others that popup (like various MySQL proxy software) to be aware when we run aground. |
@dougwilson , I will create PR on pluggable auth plugin and sha256_password auth support in this month, I have enough time to finished it this month and I'll do it, at least have a pull request with ut and integration test. And also I can make sure it will not have a breaking change which introduces the pluggable auth plugin. |
@dougwilson , I do not see any underlying packet read problem. Additional, the protocol will not be What's your insight? |
Hi @dougwilson , @sidorares , I resolved all the comments, please help to review it again. |
Great to this pr. I created an issue on this a while ago and have been using mysql2 ever since thanks to @sidorares supported it. I am looking forward to see this pr gets merged. Great job, @elemount ! |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
P.S. I love the integration test you added 👍
} | ||
|
||
ComChangeUserPacket.prototype.parse = function(parser) { | ||
this.command = parser.parseUnsignedNumber(1); | ||
this.user = parser.parseNullTerminatedString(); | ||
this.scrambleBuff = parser.parseLengthCodedBuffer(); | ||
this.database = parser.parseNullTerminatedString(); | ||
this.charsetNumber = parser.parseUnsignedNumber(1); | ||
if (!parser.reachedPacketEnd()) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why this check here? Shouldn't the charsetNumber just be outside the if like it used to be, or is there a reason behind this change?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hi @dougwilson , this is for the document COM_CHANGE_USER packet, it include the charset in the if more data
condition.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh, sorry :) I was thinking the handshake. Nevermind on this, then :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm writing a test to exercise this case, there the charsetNumber may not get populated.
lib/protocol/sequences/Handshake.js
Outdated
|
||
function HandshakeInsecureAuthError() { | ||
var err = new Error( | ||
'MySQL server is requesting the old and insecure pre-4.1 auth mechanism.' + |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks like the space that was at the end of this sentence got removed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure. Fixed it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It won't let me comment on the line, but PLUGIN_AUTH is still listed in the README under the doesn't work section.
@dougwilson , I've fixed the README issue and rebased the code. Could you merge this code after check? |
I am working through your code locally for the past half an hour or so now, with the intention of merging. I saw the README issue which is why I pinged you on it, though I see you squashed all the commits, so will need to fix up my local copy here as well. |
return Packets.ErrorPacket; | ||
} | ||
|
||
if (firstByte === 0xfe) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am fixing up this edge case where the server can actually respond here with the old password packet, of 0xfe doesn't always mean auth switch as implemented here, so just gotta add the same logic from the handshake.
I see you pushed again. I'm going to comment on the places where I am making changes to help show that I'm busy working on this. If you're making changes too, let me know so I can stop for now while you make changes... |
module.exports = AuthSwitchPacket; | ||
function AuthSwitchPacket(options) { | ||
options = options || {}; | ||
this.scrambleBuff = options.scrambleBuff; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since this is greenfield, I'm just changing these props to have the same name as in the protocol docs for sanity.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure.
} | ||
writer.writeNullTerminatedBuffer(this.scrambleBuff2); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This was a miss in the first, and since it's fixed in here, I'm trying to write a test case for this bug.
connection.config = this.config.newConnectionConfig(); | ||
connection.config.clientPluginAuth = previousClientPluginAuth; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I need to rework how this is tracked so it stops changing the hidden class of the config object.
Hey @elemount it is getting very late in my time zone and I'm likely to head out soon. I pushed up the PR #1730 code to |
@dougwilson , I add a commit on And the I'm working on the pluggable auth plugin, how should I give the pull request, based on this pull request or have an another which its code based on it? Or I should fork on another branch? |
Wow, not sure how I missed this, but was just going through open PRs / issues tonight to see where they are at. The best would have been to rebase the PR. I'm working on doing that now, because I don't see anything wrong with this PR, just needs to be rebased now.
So it would depending I guess on what is needed. Ideally it would be a separate PR either way. If it needs what is in this PR, then yea, just branch from this PR and submit a PR. Yes, at first that RP will include all changes since they are not in our |
So is this actually fixed in the newest version? |
946727b
to
37fbbdd
Compare
The new code supports mysql_native_password and mysql_old_password as potential authentication methods.
And for mysql_old_password, will still need
{inscure:true}
.It fixed #1396 and also fixed authentication against Azure Database for MySQL: #1729 .
The implementation refer the pull request #1730 .