Merge pull request #8036 from yurriy/mysql

Compatibility with MySQL clients which don't support sha2_password auth plugin
This commit is contained in:
alexey-milovidov 2019-12-09 23:11:31 +03:00 committed by GitHub
commit cce78ef018
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 87 additions and 16 deletions

View File

@ -221,7 +221,8 @@ void MySQLHandler::authenticate(const String & user_name, const String & auth_pl
{
// For compatibility with JavaScript MySQL client, Native41 authentication plugin is used when possible (if password is specified using double SHA1). Otherwise SHA256 plugin is used.
auto user = connection_context.getUser(user_name);
if (user->authentication.getType() != DB::Authentication::DOUBLE_SHA1_PASSWORD)
const DB::Authentication::Type user_auth_type = user->authentication.getType();
if (user_auth_type != DB::Authentication::DOUBLE_SHA1_PASSWORD && user_auth_type != DB::Authentication::PLAINTEXT_PASSWORD && user_auth_type != DB::Authentication::NO_PASSWORD)
{
authPluginSSL();
}

View File

@ -160,6 +160,35 @@ void Authentication::setPasswordHashBinary(const Digest & hash)
}
Digest Authentication::getPasswordDoubleSHA1() const
{
switch (type)
{
case NO_PASSWORD:
{
Poco::SHA1Engine engine;
return engine.digest();
}
case PLAINTEXT_PASSWORD:
{
Poco::SHA1Engine engine;
engine.update(getPassword());
const Digest & first_sha1 = engine.digest();
engine.update(first_sha1.data(), first_sha1.size());
return engine.digest();
}
case SHA256_PASSWORD:
throw Exception("Cannot get password double SHA1 for user with 'SHA256_PASSWORD' authentication.", ErrorCodes::BAD_ARGUMENTS);
case DOUBLE_SHA1_PASSWORD:
return password_hash;
}
throw Exception("Unknown authentication type: " + std::to_string(static_cast<int>(type)), ErrorCodes::LOGICAL_ERROR);
}
bool Authentication::isCorrectPassword(const String & password_) const
{
switch (type)
@ -168,7 +197,14 @@ bool Authentication::isCorrectPassword(const String & password_) const
return true;
case PLAINTEXT_PASSWORD:
return password_ == StringRef{reinterpret_cast<const char *>(password_hash.data()), password_hash.size()};
{
if (password_ == StringRef{reinterpret_cast<const char *>(password_hash.data()), password_hash.size()})
return true;
// For compatibility with MySQL clients which support only native authentication plugin, SHA1 can be passed instead of password.
auto password_sha1 = encodeSHA1(password_hash);
return password_ == StringRef{reinterpret_cast<const char *>(password_sha1.data()), password_sha1.size()};
}
case SHA256_PASSWORD:
return encodeSHA256(password_) == password_hash;

View File

@ -49,6 +49,10 @@ public:
void setPasswordHashBinary(const Digest & hash);
const Digest & getPasswordHashBinary() const { return password_hash; }
/// Returns SHA1(SHA1(password)) used by MySQL compatibility server for authentication.
/// Allowed to use for Type::NO_PASSWORD, Type::PLAINTEXT_PASSWORD, Type::DOUBLE_SHA1_PASSWORD.
Digest getPasswordDoubleSHA1() const;
/// Checks if the provided password is correct. Returns false if not.
bool isCorrectPassword(const String & password) const;

View File

@ -953,10 +953,7 @@ public:
auto user = context.getUser(user_name);
if (user->authentication.getType() != DB::Authentication::DOUBLE_SHA1_PASSWORD)
throw Exception("Cannot use " + getName() + " auth plugin for user " + user_name + " since its password isn't specified using double SHA1.", ErrorCodes::UNKNOWN_EXCEPTION);
Poco::SHA1Engine::Digest double_sha1_value = user->authentication.getPasswordHashBinary();
Poco::SHA1Engine::Digest double_sha1_value = user->authentication.getPasswordDoubleSHA1();
assert(double_sha1_value.size() == Poco::SHA1Engine::DIGEST_SIZE);
Poco::SHA1Engine engine;

View File

@ -6,3 +6,8 @@ services:
environment:
MYSQL_ALLOW_EMPTY_PASSWORD: 1
command: --federated --socket /var/run/mysqld/mysqld.sock
healthcheck:
test: ["CMD", "mysqladmin" ,"ping", "-h", "localhost"]
interval: 1s
timeout: 2s
retries: 100

View File

@ -15,6 +15,16 @@
<quota>default</quota>
</default>
<user_with_sha256>
<!-- echo -n abacaba | openssl dgst -sha256 !-->
<password_sha256_hex>65e84be33532fb784c48129675f9eff3a682b27168c0ea744b2cf58ee02337c5</password_sha256_hex>
<networks incl="networks" replace="replace">
<ip>::/0</ip>
</networks>
<profile>default</profile>
<quota>default</quota>
</user_with_sha256>
<user_with_double_sha1>
<!-- echo -n abacaba | openssl dgst -sha1 -binary | openssl dgst -sha1 !-->
<password_double_sha1_hex>e395796d6546b1b65db9d665cd43f0e858dd4303</password_double_sha1_hex>

View File

@ -1,14 +1,14 @@
# coding: utf-8
import docker
import datetime
import math
import os
import pytest
import subprocess
import time
import docker
import pymysql.connections
from docker.models.containers import Container
from helpers.cluster import ClickHouseCluster
@ -39,6 +39,25 @@ def mysql_client():
yield docker.from_env().containers.get(cluster.project_name + '_mysql1_1')
@pytest.fixture(scope='module')
def mysql_server(mysql_client):
"""Return MySQL container when it is healthy.
:type mysql_client: Container
:rtype: Container
"""
retries = 30
for i in range(retries):
info = mysql_client.client.api.inspect_container(mysql_client.name)
if info['State']['Health']['Status'] == 'healthy':
break
time.sleep(1)
else:
raise Exception('Mysql server has not started in %d seconds.' % retries)
return mysql_client
@pytest.fixture(scope='module')
def golang_container():
docker_compose = os.path.join(SCRIPT_DIR, 'clients', 'golang', 'docker_compose.yml')
@ -111,14 +130,14 @@ def test_mysql_client(mysql_client, server_address):
assert stdout == '\n'.join(['column', '0', '0', '1', '1', '5', '5', 'tmp_column', '0', '1', ''])
def test_mysql_federated(mysql_client, server_address):
def test_mysql_federated(mysql_server, server_address):
node.query('''DROP DATABASE IF EXISTS mysql_federated''', settings={"password": "123"})
node.query('''CREATE DATABASE mysql_federated''', settings={"password": "123"})
node.query('''CREATE TABLE mysql_federated.test (col UInt32) ENGINE = Log''', settings={"password": "123"})
node.query('''INSERT INTO mysql_federated.test VALUES (0), (1), (5)''', settings={"password": "123"})
code, (_, stderr) = mysql_client.exec_run('''
code, (_, stderr) = mysql_server.exec_run('''
mysql
-e "DROP SERVER IF EXISTS clickhouse;"
-e "CREATE SERVER clickhouse FOREIGN DATA WRAPPER mysql OPTIONS (USER 'default', PASSWORD '123', HOST '{host}', PORT {port}, DATABASE 'mysql_federated');"
@ -128,7 +147,7 @@ def test_mysql_federated(mysql_client, server_address):
assert code == 0
code, (stdout, stderr) = mysql_client.exec_run('''
code, (stdout, stderr) = mysql_server.exec_run('''
mysql
-e "CREATE TABLE mysql_federated.test(`col` int UNSIGNED) ENGINE=FEDERATED CONNECTION='clickhouse';"
-e "SELECT * FROM mysql_federated.test ORDER BY col;"
@ -136,7 +155,7 @@ def test_mysql_federated(mysql_client, server_address):
assert stdout == '\n'.join(['col', '0', '1', '5', ''])
code, (stdout, stderr) = mysql_client.exec_run('''
code, (stdout, stderr) = mysql_server.exec_run('''
mysql
-e "INSERT INTO mysql_federated.test VALUES (0), (1), (5);"
-e "SELECT * FROM mysql_federated.test ORDER BY col;"
@ -233,13 +252,12 @@ def test_php_client(server_address, php_container):
def test_mysqljs_client(server_address, nodejs_container):
code, (_, stderr) = nodejs_container.exec_run('node test.js {host} {port} default 123'.format(host=server_address, port=server_port), demux=True)
code, (_, stderr) = nodejs_container.exec_run('node test.js {host} {port} user_with_sha256 abacaba'.format(host=server_address, port=server_port), demux=True)
assert code == 1
assert 'MySQL is requesting the sha256_password authentication method, which is not supported.' in stderr
code, (_, stderr) = nodejs_container.exec_run('node test.js {host} {port} user_with_empty_password ""'.format(host=server_address, port=server_port), demux=True)
assert code == 1
assert 'MySQL is requesting the sha256_password authentication method, which is not supported.' in stderr
assert code == 0
code, (_, _) = nodejs_container.exec_run('node test.js {host} {port} user_with_double_sha1 abacaba'.format(host=server_address, port=server_port), demux=True)
assert code == 0