Pomelo.EntityFrameworkCore.MySql: Db Migrate() throws Access denied for user 'root'@'localhost'
This exception occurs only when calling Migrate/MigrateAsync
, reading data from tables, for example, is working without problems, here is my test that I used:
var count = await dbContext.TestTable.CountAsync(); // no problem
Log(count);
try
{
await dbContext.Database.MigrateAsync(); // exception!!!
}
catch (Exception ex)
{
LogError(ex);
}
count = await dbContext.TestTable.CountAsync(); // no problem
Log(count);
Exception:
fail: Microsoft.EntityFrameworkCore.Database.Connection[20004]
An error occurred using the connection to database '' on server 'localhost'.
MySql.Data.MySqlClient.MySqlException (0x80004005): Access denied for user 'root'@'localhost'
at MySqlConnector.Core.ServerSession.TryAsyncContinuation(Task`1 task) in C:\projects\mysqlconnector\src\MySqlConnector\Core\ServerSession.cs:line 1245
at System.Threading.Tasks.ContinuationResultTaskFromResultTask`2.InnerInvoke()
at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state)
--- End of stack trace from previous location where exception was thrown ---
at System.Threading.Tasks.Task.ExecuteWithThreadLocal(Task& currentTaskSlot)
--- End of stack trace from previous location where exception was thrown ---
at MySqlConnector.Core.ServerSession.ConnectAsync(ConnectionSettings cs, ILoadBalancer loadBalancer, IOBehavior ioBehavior, CancellationToken cancellationToken) in C:\projects\mysqlconnector\src\MySqlConnector\Core\ServerSession.cs:line 356
at MySql.Data.MySqlClient.MySqlConnection.CreateSessionAsync(Nullable`1 ioBehavior, CancellationToken cancellationToken) in C:\projects\mysqlconnector\src\MySqlConnector\MySql.Data.MySqlClient\MySqlConnection.cs:line 416
at MySql.Data.MySqlClient.MySqlConnection.OpenAsync(Nullable`1 ioBehavior, CancellationToken cancellationToken) in C:\projects\mysqlconnector\src\MySqlConnector\MySql.Data.MySqlClient\MySqlConnection.cs:line 175
at Microsoft.EntityFrameworkCore.Storage.RelationalConnection.OpenDbConnectionAsync(Boolean errorsExpected, CancellationToken cancellationToken)
Further technical details
MariaDB version: 10.4.8 Operating system: Ubuntu 18.04 Pomelo.EntityFrameworkCore.MySql version: 2.1.2 Microsoft.AspNetCore.App version: 2.1.2 (inside docker)
About this issue
- Original URL
- State: closed
- Created 5 years ago
- Comments: 15
Commits related to this issue
- Reuse connection string with sensitive information for master connections that are instantiated from relational connections, that are based on a `DbConnection` object instead of a connection string. F... — committed to lauxjpn/Pomelo.EntityFrameworkCore.MySql by lauxjpn 5 years ago
- Reuse connection string with sensitive information for master connections that are instantiated from relational connections, that are based on a `DbConnection` object instead of a connection string. F... — committed to lauxjpn/Pomelo.EntityFrameworkCore.MySql by lauxjpn 5 years ago
- Fix issue related to stripped security information from an internally used connection string (#916) * Reuse connection string with sensitive information for master connections that are instantiated f... — committed to PomeloFoundation/Pomelo.EntityFrameworkCore.MySql by lauxjpn 5 years ago
@waseemdev Thanks you for taking the time to report this issue! And special thanks to @bgrainger and @roji for your work and input on this issue and its solution.
This is fixed with #916.
CloneWith
is now available in 0.61.0. I think the code to use it would be a bit shorter and look like this:@bgrainger Yes, that will work!
MySqlConnection.Clone
was implemented in 0.60.0: https://github.com/mysql-net/MySqlConnector/issues/720This should copy the original connection string, including authentication information. If you have code that looks like:
with 0.60.0, you may be able to replace it with:
Would this fix the problem here?
@lauxjpn yeah, this is indeed an issue. In Npgsql I worked around this by having a CloneWith method on NpgsqlConnection, which accepts a new connection string but keeps authentication information from the clone source (password, SSL/TLS callbacks if relevant). This also allows you to take an existing connection string and change options on it (e.g. make it non-pooling) while conserving auth options, regardless of whether PersistSecurityInfo was set to false.
/cc @bgrainger
The issue
The underlying issue with sharing a single connection and calling
MigrateAsync()
is, that the methods responsible for checking the databases existence and so on, create their own temporary connection object for their operations (MySqlRelationalConnection.CreateMasterConnection()
).This temporary connection object needs to get a connection string from somewhere to establish its connection. If you called
.UseMySql()
with a connection string as a parameter, it will reuse that one.But if you called it with an already existing connection object, it will retrieve the connection string used for this original connection object from the object itself at the point in time when the temporary connection object needs to be created.
If at this point in time, the original connection object has already been used, sensitive connection information like the password will have already been stripped from the connection string of the original connection for security reasons.
As a result, the connection string used for the temporary connection object might not contain all the necessary information anymore and opening the temporary connection might fail.
Also, because a temporary connection object is used internally (and the EF Core code does not seem to take steps here to enlist in any transaction), this object will not be part of your transaction.
Workaround
You can specify
PersistSecurityInfo=True
in your connection string for your shared/original connection object and keep the password in the string. This will make theMigrateAsync()
call work, but it will still not enlist in your transaction.If you need the migration commands in your transaction, you can upgrade your code to
3.0.0
, as we are supporting ambient transactions in3.0.0
, which should work here, I think.Steps for us to fix
The easiest way to ensure that we reuse a connection string as best as possible, is to read it from the original connection object the moment a
MySqlRelationalConnection
is created and override theConnectionString
property to instead always return our early read one.This ensures, that even without specifying
PersistSecurityInfo=True
explicitly, a master connection will be created without issues later on, as long as the original connection had not already been opened at the time it was used as a parameter for.UseMySql()
.This should be the expected behavior, because using a second/master connection for internal operations is an implementation detail that most users will be unaware about.
Since using a master connection seems to be the default implementation for providers, this issue might concern other providers as well. /cc @AndriySvyryd @roji