mirror of
https://github.com/ClickHouse/ClickHouse.git
synced 2024-12-03 13:02:00 +00:00
Merge pull request #8036 from yurriy/mysql
Compatibility with MySQL clients which don't support sha2_password auth plugin
This commit is contained in:
commit
cce78ef018
@ -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();
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
|
@ -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>
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user