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

allow to use arbitrary plugin as first auth method #560

Open
sidorares opened this issue May 15, 2017 · 13 comments
Open

allow to use arbitrary plugin as first auth method #560

sidorares opened this issue May 15, 2017 · 13 comments

Comments

@sidorares
Copy link
Owner

currently only allowed connect method initially is mysql_native_password. Some servers can potentially prefer to start with custom auth immediately, instead of rejecting mysql_native_password and doing AUTH_SWITH_HANDLER sequence afterwards. Also some servers can be configured to allow 'plugin based auth' but not 'auth switch request' - those are two different capabilities flags

  1. respect handshake packet plugin name
  2. deprecate authSwitchHandler and rename it to be authPluginHandler
  3. provide default handler for mysql_native_password

also need to think of something to make it easy to chain handlers:

const mysqlIamAuth = require('mysql-iam-auth'); // imaginary, does not exist
const mysqMyCustomAuth = require('@internal/customauth');

const pool = mysql2.createPool({
  authPluginHandler: combineAuthHandlers(mysqlIamAuth, mysqMyCustomAuth)
})

ref http://stackoverflow.com/questions/43448563/connecting-to-mariadb-with-nodejs-over-ssl-with-clear-text-password/43450396#43450396

@sidorares
Copy link
Owner Author

sidorares commented Jun 30, 2017

thinking about merging plugin auth and auth switch. Maybe api like this:

const mysql2 = require('mysql');
const pool = mysql2.createPool({
  authPlugins: {
     sha256_password: (data, cb) => { /* ... probably going to be included by default. You should be able to disable it by specifying  authPlugins: { sha256_password: null } */ },
     mysql_clear_password: mysql2.authPlugins.mysql_clear_password(validateFunction), // not enabled by default but included with driver and documented
  }, 

  // better name for this option? "defaultPlugin" ? "initialConnectionPlugin" ?
  // this is plugin name sent with initial connection (also handler executed at connection time and result of the handler included with initial connection"
  //  error if name does not match authPlugins keys ( or built-in plugins ). Default to "mysql_native_password" if not specified
  connectAuthPluginName: 'mysql_native_password'  // not used if server does not support PLUGIN_AUTH
                                                                               // should it error if specified and no server support for PLUGIN_AUTH ? 
  // some way to handle dynamic plugin names? currently possible with authSwitchHandler but I want to deprecate it
}

@normano
Copy link
Contributor

normano commented Jan 5, 2019

defaultAuthPlugin sounds like a good name to me. An auth plugin should likely be able to write it's own kinds of packets though or at least find some way for the plugin/function to return the next handler for the command. Considering how the caching_sha2 one appears to want a response to the fullauthentication request and there is also the fast auth path (https://dev.mysql.com/doc/dev/mysql-server/8.0.4/page_caching_sha2_authentication_exchanges.html), you'd want the plugin to handle all success responses until it says it is done (return null).

@sidorares
Copy link
Owner Author

sidorares commented Jan 5, 2019

it's own kinds of packets though

I think it's ok to assume plugin is only able to send data or null, afaik it's never allowed to send any other packet than AuthMoreData or OK, but there can be possible multiple exchanges and just having function as plugin is not enough, we need instanse of object that updates it state as it progresses from AuthMoreData to final OK

@normano
Copy link
Contributor

normano commented Jan 5, 2019

I think function that returns the next handler is fine. You as the middle man don't care for state, only what comes next. If I write a plugin then I'll worry about state on my side and know only to return to you what should be called next.

Object is fine too but then you have to figure out how to structure the methods it should call right? Not too big a fan, but done right it will work.

@terrisgit
Copy link

Is this stable? Is there a better example? My use case is AWS RDS IAM tokens.

@sidorares
Copy link
Owner Author

sidorares commented Feb 20, 2020

I believe new plugin auth api is stable. Currently connection is always started with mysql_native_password and later uses plugin via AuthSwitch packets exchange. This issue tracks possible change to allow to connect with custom plugin in the initial auth packet, saving couple of network roundtrips time

Good example in the answer here: https://stackoverflow.com/a/60013378/705115

( I'll copy it here as well with some minor changes )

const iamTokenPlugin =({connection, command}) => (authPluginDataFromServer) => {
   return signer.getAuthToken({
                region: '...',
                hostname: '...',
                port: '...',
                username: '...'
            })
}

mysql.createPool({
    ...,
    ssl: 'Amazon RDS',
    authPlugins: {
        mysql_clear_password: iamTokenPlugin
    }
});

The reason plugin has ({connection, command}) => Buffer => return Promise<Buffer> signature is to allow plugins to have internal state and update it as a result to AuthSwitchMoreData commands.

see how plugin is "instantinated" here

connection._authPlugin = authPlugin({ connection, command });
and later called (potentially many times) with data here
Promise.resolve(connection._authPlugin(data)).then(data => {
if (data) {
connection.writePacket(new Packets.AuthSwitchResponse(data).toPacket());
}

See mysql AuthSwitch docs here - https://dev.mysql.com/doc/internals/en/connection-phase-packets.html#packet-Protocol::AuthSwitchRequest

@terrisgit
Copy link

terrisgit commented Mar 7, 2020

Amazon has been planning for some time to change their certificates for TLS to RDS, at least for mySQL.

If your RDS server has been upgraded (which Amazon will do in June 2020), when attempting IAM token login, you will get the error:

Error: 139965154551680:error:1425F102:SSL routines:ssl_choose_client_version:unsupported protocol:../deps/openssl/openssl/ssl/statem/statem_lib.c:1929:

If you use NodeJS 12 or lower, start node with --tls-min-v1.0

@silverbullettruck2001
Copy link
Contributor

silverbullettruck2001 commented Jan 23, 2022

@sidorares I am trying to use this with AWS RDS IAM and when I execute it the error that is returned is TypeError: authPlugin is not a function. How can I resolve this? Here is my code:

const mysql = require('mysql2');

// create the connection to database
const pool = mysql.createPool({
  host: 'www.myawsDatabase.com',
  user: 'someUsername',
  database: 'databaseServer',
  port: 3306,
  ssl: 'Amazon RDS',
  authPlugins: {
    mysql_clear_password:
      'mytoken',
  },
});

setInterval(() => {
  for (let i = 0; i < 5; ++i) {
    pool.query((err, db) => {
      console.log(rows, fields);
      // Connection is automatically released once query resolves
    });
  }
}, 1000);

setInterval(() => {
  for (let i = 0; i < 5; ++i) {
    pool.getConnection((err, db) => {
      db.query(
        'SELECT * FROM `randomTable`',
        (err, rows, fields) => {
          console.log(rows, fields);
          db.release();
        }
      );
    });
  }
}, 1000);

@terrisgit
Copy link

terrisgit commented Jan 23, 2022

@silverbullettruck2001 : Provide a function to mysql_clear_password . See the comment above from Feb 20 2020.

@silverbullettruck2001
Copy link
Contributor

silverbullettruck2001 commented Jan 24, 2022

@terrisgit Here is what I put together. I am trying to avoid using the signer library and just provide the raw token I have pre-generated to establish the connection. With the following code it is throwing this error: Uncaught TypeError [ERR_INVALID_ARG_TYPE]: The first argument must be of type string or an instance of Buffer, ArrayBuffer, or Array or an Array-like Object. Received type function ([Function (anonymous)]). Is there a way to accomplish what I am trying to do?

const mysql = require('mysql2');

const iamTokenPlugin =
  ({ connection, command }) =>
  (authPluginDataFromServer) => {
    const myToken = 'myToken';
    return myToken;
  };

// create the connection to database
const pool = mysql.createPool({
  host: 'www.myawsDatabase.com',
  user: 'someUsername',
  database: 'databaseServer',
  port: 3306,
  ssl: 'Amazon RDS',
  authPlugins: {
    mysql_clear_password:  iamTokenPlugin
  },
});

setInterval(() => {
  for (let i = 0; i < 5; ++i) {
    pool.query((err, db) => {
      console.log(rows, fields);
      // Connection is automatically released once query resolves
    });
  }
}, 1000);

setInterval(() => {
  for (let i = 0; i < 5; ++i) {
    pool.getConnection((err, db) => {
      db.query(
        'SELECT * FROM `randomTable`',
        (err, rows, fields) => {
          console.log(rows, fields);
          db.release();
        }
      );
    });
  }
}, 1000);

@silverbullettruck2001
Copy link
Contributor

silverbullettruck2001 commented Jan 25, 2022

@sidorares @terrisgit I made some progress, but still not working...It now errors with:
"Connection lost: The server closed the connection.". Any suggestions on how to get this to work appropriately? I have validated that the server is up and that I can connect to using mySQL Workbench using the same connection information and token.

Here's my latest code:

import mysql from 'mysql2';

const iamTokenPlugin =
  ({ connection, command }) =>
  (authPluginDataFromServer) => {
    const myToken =
      'somerandomtoken';
    return Promise.resolve(authPluginDataFromServer.write(myToken));
  };

// create the connection to database
const pool = mysql.createPool({
  host: 'www.myawsDatabase.com',
  user: 'someUsername',
  database: 'databaseServer',
  port: 3306,
  ssl: 'Amazon RDS',
  authPlugins: {
    mysql_clear_password:  iamTokenPlugin
  },
});

pool.getConnection((err, db) => {
  db.query(
    'SELECT * FROM `randomTable`',
    (err, rows, fields) => {
      console.log(err);
      console.log(rows, fields);
      db.release();
    }
  );
});

@terrisgit
Copy link

@silverbullettruck2001 please see this mysql2 helper package that supports AWS RDS passwordless connections. Pooling is optional. https://www.npmjs.com/package/@goodware/mysql

@silverbullettruck2001
Copy link
Contributor

@terrisgit I reviewed this helper package, but it also implements RDS.Signer to generate the token. I would like to not use that library and just pass in a self-generated token. I haven't been able to figure out how to do this though. Do you have any suggestions on how that could be done?

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

No branches or pull requests

4 participants