mirror of
https://github.com/ClickHouse/ClickHouse.git
synced 2024-12-12 09:22:05 +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.
|
// 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);
|
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();
|
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
|
bool Authentication::isCorrectPassword(const String & password_) const
|
||||||
{
|
{
|
||||||
switch (type)
|
switch (type)
|
||||||
@ -168,7 +197,14 @@ bool Authentication::isCorrectPassword(const String & password_) const
|
|||||||
return true;
|
return true;
|
||||||
|
|
||||||
case PLAINTEXT_PASSWORD:
|
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:
|
case SHA256_PASSWORD:
|
||||||
return encodeSHA256(password_) == password_hash;
|
return encodeSHA256(password_) == password_hash;
|
||||||
|
@ -49,6 +49,10 @@ public:
|
|||||||
void setPasswordHashBinary(const Digest & hash);
|
void setPasswordHashBinary(const Digest & hash);
|
||||||
const Digest & getPasswordHashBinary() const { return password_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.
|
/// Checks if the provided password is correct. Returns false if not.
|
||||||
bool isCorrectPassword(const String & password) const;
|
bool isCorrectPassword(const String & password) const;
|
||||||
|
|
||||||
|
@ -953,10 +953,7 @@ public:
|
|||||||
|
|
||||||
auto user = context.getUser(user_name);
|
auto user = context.getUser(user_name);
|
||||||
|
|
||||||
if (user->authentication.getType() != DB::Authentication::DOUBLE_SHA1_PASSWORD)
|
Poco::SHA1Engine::Digest double_sha1_value = user->authentication.getPasswordDoubleSHA1();
|
||||||
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();
|
|
||||||
assert(double_sha1_value.size() == Poco::SHA1Engine::DIGEST_SIZE);
|
assert(double_sha1_value.size() == Poco::SHA1Engine::DIGEST_SIZE);
|
||||||
|
|
||||||
Poco::SHA1Engine engine;
|
Poco::SHA1Engine engine;
|
||||||
|
@ -6,3 +6,8 @@ services:
|
|||||||
environment:
|
environment:
|
||||||
MYSQL_ALLOW_EMPTY_PASSWORD: 1
|
MYSQL_ALLOW_EMPTY_PASSWORD: 1
|
||||||
command: --federated --socket /var/run/mysqld/mysqld.sock
|
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>
|
<quota>default</quota>
|
||||||
</default>
|
</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>
|
<user_with_double_sha1>
|
||||||
<!-- echo -n abacaba | openssl dgst -sha1 -binary | openssl dgst -sha1 !-->
|
<!-- echo -n abacaba | openssl dgst -sha1 -binary | openssl dgst -sha1 !-->
|
||||||
<password_double_sha1_hex>e395796d6546b1b65db9d665cd43f0e858dd4303</password_double_sha1_hex>
|
<password_double_sha1_hex>e395796d6546b1b65db9d665cd43f0e858dd4303</password_double_sha1_hex>
|
||||||
|
@ -1,14 +1,14 @@
|
|||||||
# coding: utf-8
|
# coding: utf-8
|
||||||
|
|
||||||
|
import docker
|
||||||
import datetime
|
import datetime
|
||||||
import math
|
import math
|
||||||
import os
|
import os
|
||||||
import pytest
|
import pytest
|
||||||
import subprocess
|
import subprocess
|
||||||
import time
|
import time
|
||||||
|
|
||||||
import docker
|
|
||||||
import pymysql.connections
|
import pymysql.connections
|
||||||
|
|
||||||
from docker.models.containers import Container
|
from docker.models.containers import Container
|
||||||
|
|
||||||
from helpers.cluster import ClickHouseCluster
|
from helpers.cluster import ClickHouseCluster
|
||||||
@ -39,6 +39,25 @@ def mysql_client():
|
|||||||
yield docker.from_env().containers.get(cluster.project_name + '_mysql1_1')
|
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')
|
@pytest.fixture(scope='module')
|
||||||
def golang_container():
|
def golang_container():
|
||||||
docker_compose = os.path.join(SCRIPT_DIR, 'clients', 'golang', 'docker_compose.yml')
|
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', ''])
|
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('''DROP DATABASE IF EXISTS mysql_federated''', settings={"password": "123"})
|
||||||
node.query('''CREATE DATABASE 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('''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"})
|
node.query('''INSERT INTO mysql_federated.test VALUES (0), (1), (5)''', settings={"password": "123"})
|
||||||
|
|
||||||
|
code, (_, stderr) = mysql_server.exec_run('''
|
||||||
code, (_, stderr) = mysql_client.exec_run('''
|
|
||||||
mysql
|
mysql
|
||||||
-e "DROP SERVER IF EXISTS clickhouse;"
|
-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');"
|
-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
|
assert code == 0
|
||||||
|
|
||||||
code, (stdout, stderr) = mysql_client.exec_run('''
|
code, (stdout, stderr) = mysql_server.exec_run('''
|
||||||
mysql
|
mysql
|
||||||
-e "CREATE TABLE mysql_federated.test(`col` int UNSIGNED) ENGINE=FEDERATED CONNECTION='clickhouse';"
|
-e "CREATE TABLE mysql_federated.test(`col` int UNSIGNED) ENGINE=FEDERATED CONNECTION='clickhouse';"
|
||||||
-e "SELECT * FROM mysql_federated.test ORDER BY col;"
|
-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', ''])
|
assert stdout == '\n'.join(['col', '0', '1', '5', ''])
|
||||||
|
|
||||||
code, (stdout, stderr) = mysql_client.exec_run('''
|
code, (stdout, stderr) = mysql_server.exec_run('''
|
||||||
mysql
|
mysql
|
||||||
-e "INSERT INTO mysql_federated.test VALUES (0), (1), (5);"
|
-e "INSERT INTO mysql_federated.test VALUES (0), (1), (5);"
|
||||||
-e "SELECT * FROM mysql_federated.test ORDER BY col;"
|
-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):
|
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 code == 1
|
||||||
assert 'MySQL is requesting the sha256_password authentication method, which is not supported.' in stderr
|
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)
|
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 code == 0
|
||||||
assert 'MySQL is requesting the sha256_password authentication method, which is not supported.' in stderr
|
|
||||||
|
|
||||||
code, (_, _) = nodejs_container.exec_run('node test.js {host} {port} user_with_double_sha1 abacaba'.format(host=server_address, port=server_port), demux=True)
|
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
|
assert code == 0
|
||||||
|
Loading…
Reference in New Issue
Block a user