mirror of
https://github.com/ClickHouse/ClickHouse.git
synced 2024-11-21 15:12:02 +00:00
Merge pull request #36172 from DevTeamBK/feature-password
password hash salt feature
This commit is contained in:
commit
a7318b3a96
@ -16,18 +16,6 @@
|
||||
#include <Interpreters/Access/InterpreterGrantQuery.h>
|
||||
#include <Interpreters/Access/InterpreterShowCreateAccessEntityQuery.h>
|
||||
#include <Interpreters/Access/InterpreterShowGrantsQuery.h>
|
||||
#include <Parsers/Access/ASTCreateQuotaQuery.h>
|
||||
#include <Parsers/Access/ASTCreateRoleQuery.h>
|
||||
#include <Parsers/Access/ASTCreateRowPolicyQuery.h>
|
||||
#include <Parsers/Access/ASTCreateSettingsProfileQuery.h>
|
||||
#include <Parsers/Access/ASTCreateUserQuery.h>
|
||||
#include <Parsers/Access/ASTGrantQuery.h>
|
||||
#include <Parsers/Access/ParserCreateQuotaQuery.h>
|
||||
#include <Parsers/Access/ParserCreateRoleQuery.h>
|
||||
#include <Parsers/Access/ParserCreateRowPolicyQuery.h>
|
||||
#include <Parsers/Access/ParserCreateSettingsProfileQuery.h>
|
||||
#include <Parsers/Access/ParserCreateUserQuery.h>
|
||||
#include <Parsers/Access/ParserGrantQuery.h>
|
||||
#include <Parsers/formatAST.h>
|
||||
#include <Parsers/parseQuery.h>
|
||||
#include <boost/range/algorithm/copy.hpp>
|
||||
@ -40,39 +28,6 @@ namespace ErrorCodes
|
||||
extern const int INCORRECT_ACCESS_ENTITY_DEFINITION;
|
||||
}
|
||||
|
||||
namespace
|
||||
{
|
||||
/// Special parser for the 'ATTACH access entity' queries.
|
||||
class ParserAttachAccessEntity : public IParserBase
|
||||
{
|
||||
protected:
|
||||
const char * getName() const override { return "ATTACH access entity query"; }
|
||||
|
||||
bool parseImpl(Pos & pos, ASTPtr & node, Expected & expected) override
|
||||
{
|
||||
ParserCreateUserQuery create_user_p;
|
||||
ParserCreateRoleQuery create_role_p;
|
||||
ParserCreateRowPolicyQuery create_policy_p;
|
||||
ParserCreateQuotaQuery create_quota_p;
|
||||
ParserCreateSettingsProfileQuery create_profile_p;
|
||||
ParserGrantQuery grant_p;
|
||||
|
||||
create_user_p.useAttachMode();
|
||||
create_role_p.useAttachMode();
|
||||
create_policy_p.useAttachMode();
|
||||
create_quota_p.useAttachMode();
|
||||
create_profile_p.useAttachMode();
|
||||
grant_p.useAttachMode();
|
||||
|
||||
return create_user_p.parse(pos, node, expected) || create_role_p.parse(pos, node, expected)
|
||||
|| create_policy_p.parse(pos, node, expected) || create_quota_p.parse(pos, node, expected)
|
||||
|| create_profile_p.parse(pos, node, expected) || grant_p.parse(pos, node, expected);
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
|
||||
String serializeAccessEntity(const IAccessEntity & entity)
|
||||
{
|
||||
/// Build list of ATTACH queries.
|
||||
|
@ -1,10 +1,51 @@
|
||||
#pragma once
|
||||
|
||||
#include <Parsers/Access/ASTCreateQuotaQuery.h>
|
||||
#include <Parsers/Access/ASTCreateRoleQuery.h>
|
||||
#include <Parsers/Access/ASTCreateRowPolicyQuery.h>
|
||||
#include <Parsers/Access/ASTCreateSettingsProfileQuery.h>
|
||||
#include <Parsers/Access/ASTCreateUserQuery.h>
|
||||
#include <Parsers/Access/ASTGrantQuery.h>
|
||||
#include <Parsers/Access/ParserCreateQuotaQuery.h>
|
||||
#include <Parsers/Access/ParserCreateRoleQuery.h>
|
||||
#include <Parsers/Access/ParserCreateRowPolicyQuery.h>
|
||||
#include <Parsers/Access/ParserCreateSettingsProfileQuery.h>
|
||||
#include <Parsers/Access/ParserCreateUserQuery.h>
|
||||
#include <Parsers/Access/ParserGrantQuery.h>
|
||||
#include <base/types.h>
|
||||
#include <memory>
|
||||
|
||||
namespace DB
|
||||
{
|
||||
|
||||
/// Special parser for the 'ATTACH access entity' queries.
|
||||
class ParserAttachAccessEntity : public IParserBase
|
||||
{
|
||||
protected:
|
||||
const char * getName() const override { return "ATTACH access entity query"; }
|
||||
|
||||
bool parseImpl(Pos & pos, ASTPtr & node, Expected & expected) override
|
||||
{
|
||||
ParserCreateUserQuery create_user_p;
|
||||
ParserCreateRoleQuery create_role_p;
|
||||
ParserCreateRowPolicyQuery create_policy_p;
|
||||
ParserCreateQuotaQuery create_quota_p;
|
||||
ParserCreateSettingsProfileQuery create_profile_p;
|
||||
ParserGrantQuery grant_p;
|
||||
|
||||
create_user_p.useAttachMode();
|
||||
create_role_p.useAttachMode();
|
||||
create_policy_p.useAttachMode();
|
||||
create_quota_p.useAttachMode();
|
||||
create_profile_p.useAttachMode();
|
||||
grant_p.useAttachMode();
|
||||
|
||||
return create_user_p.parse(pos, node, expected) || create_role_p.parse(pos, node, expected)
|
||||
|| create_policy_p.parse(pos, node, expected) || create_quota_p.parse(pos, node, expected)
|
||||
|| create_profile_p.parse(pos, node, expected) || grant_p.parse(pos, node, expected);
|
||||
}
|
||||
};
|
||||
|
||||
struct IAccessEntity;
|
||||
using AccessEntityPtr = std::shared_ptr<const IAccessEntity>;
|
||||
|
||||
|
@ -31,9 +31,9 @@ namespace
|
||||
return (Util::encodeDoubleSHA1(password) == password_double_sha1);
|
||||
}
|
||||
|
||||
bool checkPasswordSHA256(const std::string_view & password, const Digest & password_sha256)
|
||||
bool checkPasswordSHA256(const std::string_view & password, const Digest & password_sha256, const String & salt)
|
||||
{
|
||||
return Util::encodeSHA256(password) == password_sha256;
|
||||
return Util::encodeSHA256(String(password).append(salt)) == password_sha256;
|
||||
}
|
||||
|
||||
bool checkPasswordDoubleSHA1MySQL(const std::string_view & scramble, const std::string_view & scrambled_password, const Digest & password_double_sha1)
|
||||
@ -132,7 +132,7 @@ bool Authentication::areCredentialsValid(const Credentials & credentials, const
|
||||
return checkPasswordPlainText(basic_credentials->getPassword(), auth_data.getPasswordHashBinary());
|
||||
|
||||
case AuthenticationType::SHA256_PASSWORD:
|
||||
return checkPasswordSHA256(basic_credentials->getPassword(), auth_data.getPasswordHashBinary());
|
||||
return checkPasswordSHA256(basic_credentials->getPassword(), auth_data.getPasswordHashBinary(), auth_data.getSalt());
|
||||
|
||||
case AuthenticationType::DOUBLE_SHA1_PASSWORD:
|
||||
return checkPasswordDoubleSHA1(basic_credentials->getPassword(), auth_data.getPasswordHashBinary());
|
||||
|
@ -210,6 +210,17 @@ void AuthenticationData::setPasswordHashBinary(const Digest & hash)
|
||||
throw Exception("setPasswordHashBinary(): authentication type " + toString(type) + " not supported", ErrorCodes::NOT_IMPLEMENTED);
|
||||
}
|
||||
|
||||
void AuthenticationData::setSalt(String salt_)
|
||||
{
|
||||
if (type != AuthenticationType::SHA256_PASSWORD)
|
||||
throw Exception("setSalt(): authentication type " + toString(type) + " not supported", ErrorCodes::NOT_IMPLEMENTED);
|
||||
salt = std::move(salt_);
|
||||
}
|
||||
|
||||
String AuthenticationData::getSalt() const
|
||||
{
|
||||
return salt;
|
||||
}
|
||||
|
||||
void AuthenticationData::setSSLCertificateCommonNames(boost::container::flat_set<String> common_names_)
|
||||
{
|
||||
|
@ -76,6 +76,10 @@ public:
|
||||
void setPasswordHashBinary(const Digest & hash);
|
||||
const Digest & getPasswordHashBinary() const { return password_hash; }
|
||||
|
||||
/// Sets the salt in String form.
|
||||
void setSalt(String salt);
|
||||
String getSalt() const;
|
||||
|
||||
/// Sets the server name for authentication type LDAP.
|
||||
const String & getLDAPServerName() const { return ldap_server_name; }
|
||||
void setLDAPServerName(const String & name) { ldap_server_name = name; }
|
||||
@ -106,6 +110,7 @@ private:
|
||||
String ldap_server_name;
|
||||
String kerberos_realm;
|
||||
boost::container::flat_set<String> ssl_certificate_common_names;
|
||||
String salt;
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -36,6 +36,7 @@ namespace
|
||||
String auth_type_name = AuthenticationTypeInfo::get(auth_type).name;
|
||||
String value_prefix;
|
||||
std::optional<String> value;
|
||||
std::optional<String> salt;
|
||||
const boost::container::flat_set<String> * values = nullptr;
|
||||
|
||||
if (show_password ||
|
||||
@ -56,6 +57,10 @@ namespace
|
||||
auth_type_name = "sha256_hash";
|
||||
value_prefix = "BY";
|
||||
value = auth_data.getPasswordHashHex();
|
||||
if (!auth_data.getSalt().empty())
|
||||
{
|
||||
salt = auth_data.getSalt();
|
||||
}
|
||||
break;
|
||||
}
|
||||
case AuthenticationType::DOUBLE_SHA1_PASSWORD:
|
||||
@ -107,6 +112,8 @@ namespace
|
||||
if (value)
|
||||
{
|
||||
settings.ostr << " " << quoteString(*value);
|
||||
if (salt)
|
||||
settings.ostr << " SALT " << quoteString(*salt);
|
||||
}
|
||||
else if (values)
|
||||
{
|
||||
|
@ -16,7 +16,12 @@
|
||||
#include <base/range.h>
|
||||
#include <boost/algorithm/string/predicate.hpp>
|
||||
#include <base/insertAtEnd.h>
|
||||
|
||||
#include <Common/config.h>
|
||||
#include <Common/hex.h>
|
||||
#if USE_SSL
|
||||
# include <openssl/crypto.h>
|
||||
# include <openssl/rand.h>
|
||||
#endif
|
||||
|
||||
namespace DB
|
||||
{
|
||||
@ -34,7 +39,7 @@ namespace
|
||||
}
|
||||
|
||||
|
||||
bool parseAuthenticationData(IParserBase::Pos & pos, Expected & expected, AuthenticationData & auth_data)
|
||||
bool parseAuthenticationData(IParserBase::Pos & pos, Expected & expected, bool id_mode, AuthenticationData & auth_data)
|
||||
{
|
||||
return IParserBase::wrapParseImpl(pos, [&]
|
||||
{
|
||||
@ -99,14 +104,22 @@ namespace
|
||||
}
|
||||
|
||||
String value;
|
||||
String parsed_salt;
|
||||
boost::container::flat_set<String> common_names;
|
||||
if (expect_password || expect_hash)
|
||||
{
|
||||
ASTPtr ast;
|
||||
if (!ParserKeyword{"BY"}.ignore(pos, expected) || !ParserStringLiteral{}.parse(pos, ast, expected))
|
||||
return false;
|
||||
|
||||
value = ast->as<const ASTLiteral &>().value.safeGet<String>();
|
||||
|
||||
if (id_mode && expect_hash)
|
||||
{
|
||||
if (ParserKeyword{"SALT"}.ignore(pos, expected) && ParserStringLiteral{}.parse(pos, ast, expected))
|
||||
{
|
||||
parsed_salt = ast->as<const ASTLiteral &>().value.safeGet<String>();
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (expect_ldap_server_name)
|
||||
{
|
||||
@ -141,6 +154,34 @@ namespace
|
||||
}
|
||||
|
||||
auth_data = AuthenticationData{*type};
|
||||
if (auth_data.getType() == AuthenticationType::SHA256_PASSWORD)
|
||||
{
|
||||
if (!parsed_salt.empty())
|
||||
{
|
||||
auth_data.setSalt(parsed_salt);
|
||||
}
|
||||
else if (expect_password)
|
||||
{
|
||||
#if USE_SSL
|
||||
///generate and add salt here
|
||||
///random generator FIPS complaint
|
||||
uint8_t key[32];
|
||||
RAND_bytes(key, sizeof(key));
|
||||
String salt;
|
||||
salt.resize(sizeof(key) * 2);
|
||||
char * buf_pos = salt.data();
|
||||
for (uint8_t k : key)
|
||||
{
|
||||
writeHexByteUppercase(k, buf_pos);
|
||||
buf_pos += 2;
|
||||
}
|
||||
value.append(salt);
|
||||
auth_data.setSalt(salt);
|
||||
#else
|
||||
///if USE_SSL is not defined, Exception thrown later
|
||||
#endif
|
||||
}
|
||||
}
|
||||
if (expect_password)
|
||||
auth_data.setPassword(value);
|
||||
else if (expect_hash)
|
||||
@ -393,7 +434,7 @@ bool ParserCreateUserQuery::parseImpl(Pos & pos, ASTPtr & node, Expected & expec
|
||||
if (!auth_data)
|
||||
{
|
||||
AuthenticationData new_auth_data;
|
||||
if (parseAuthenticationData(pos, expected, new_auth_data))
|
||||
if (parseAuthenticationData(pos, expected, attach_mode, new_auth_data))
|
||||
{
|
||||
auth_data = std::move(new_auth_data);
|
||||
continue;
|
||||
|
@ -9,9 +9,9 @@
|
||||
#include <Parsers/ParserQueryWithOutput.h>
|
||||
#include <Parsers/formatAST.h>
|
||||
#include <Parsers/parseQuery.h>
|
||||
|
||||
#include "Access/AccessEntityIO.h"
|
||||
#include <string_view>
|
||||
|
||||
#include <regex>
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
namespace
|
||||
@ -20,6 +20,7 @@ using namespace DB;
|
||||
using namespace std::literals;
|
||||
}
|
||||
|
||||
|
||||
struct ParserTestCase
|
||||
{
|
||||
const std::string_view input_text;
|
||||
@ -48,9 +49,32 @@ TEST_P(ParserTest, parseQuery)
|
||||
|
||||
if (expected_ast)
|
||||
{
|
||||
ASTPtr ast;
|
||||
ASSERT_NO_THROW(ast = parseQuery(*parser, input_text.begin(), input_text.end(), 0, 0));
|
||||
EXPECT_EQ(expected_ast, serializeAST(*ast->clone(), false));
|
||||
if (std::string(expected_ast).starts_with("throws"))
|
||||
{
|
||||
EXPECT_THROW(parseQuery(*parser, input_text.begin(), input_text.end(), 0, 0), DB::Exception);
|
||||
}
|
||||
else
|
||||
{
|
||||
ASTPtr ast;
|
||||
ASSERT_NO_THROW(ast = parseQuery(*parser, input_text.begin(), input_text.end(), 0, 0));
|
||||
if (std::string("CREATE USER or ALTER USER query") != parser->getName()
|
||||
&& std::string("ATTACH access entity query") != parser->getName())
|
||||
{
|
||||
EXPECT_EQ(expected_ast, serializeAST(*ast->clone(), false));
|
||||
}
|
||||
else
|
||||
{
|
||||
if (input_text.starts_with("ATTACH"))
|
||||
{
|
||||
auto salt = (dynamic_cast<const ASTCreateUserQuery *>(ast.get())->auth_data)->getSalt();
|
||||
EXPECT_TRUE(std::regex_match(salt, std::regex(expected_ast)));
|
||||
}
|
||||
else
|
||||
{
|
||||
EXPECT_TRUE(std::regex_match(serializeAST(*ast->clone(), false), std::regex(expected_ast)));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -226,3 +250,35 @@ INSTANTIATE_TEST_SUITE_P(ParserCreateDatabaseQuery, ParserTest,
|
||||
"CREATE DATABASE db\nENGINE = Foo\nSETTINGS a = 1, b = 2\nTABLE OVERRIDE `a`\n(\n ORDER BY (`id`, `version`)\n)\nCOMMENT 'db comment'"
|
||||
}
|
||||
})));
|
||||
|
||||
INSTANTIATE_TEST_SUITE_P(ParserCreateUserQuery, ParserTest,
|
||||
::testing::Combine(
|
||||
::testing::Values(std::make_shared<ParserCreateUserQuery>()),
|
||||
::testing::ValuesIn(std::initializer_list<ParserTestCase>{
|
||||
{
|
||||
"CREATE USER user1 IDENTIFIED WITH sha256_password BY 'qwe123'",
|
||||
"CREATE USER user1 IDENTIFIED WITH sha256_hash BY '[A-Za-z0-9]{64}' SALT '[A-Za-z0-9]{64}'"
|
||||
},
|
||||
{
|
||||
"ALTER USER user1 IDENTIFIED WITH sha256_password BY 'qwe123'",
|
||||
"ALTER USER user1 IDENTIFIED WITH sha256_hash BY '[A-Za-z0-9]{64}' SALT '[A-Za-z0-9]{64}'"
|
||||
},
|
||||
{
|
||||
"CREATE USER user1 IDENTIFIED WITH sha256_password BY 'qwe123' SALT 'EFFD7F6B03B3EA68B8F86C1E91614DD50E42EB31EF7160524916444D58B5E264'",
|
||||
"throws Syntax error"
|
||||
}
|
||||
})));
|
||||
|
||||
INSTANTIATE_TEST_SUITE_P(ParserAttachUserQuery, ParserTest,
|
||||
::testing::Combine(
|
||||
::testing::Values(std::make_shared<ParserAttachAccessEntity>()),
|
||||
::testing::ValuesIn(std::initializer_list<ParserTestCase>{
|
||||
{
|
||||
"ATTACH USER user1 IDENTIFIED WITH sha256_hash BY '2CC4880302693485717D34E06046594CFDFE425E3F04AA5A094C4AABAB3CB0BF' SALT 'EFFD7F6B03B3EA68B8F86C1E91614DD50E42EB31EF7160524916444D58B5E264';",
|
||||
"^[A-Za-z0-9]{64}$"
|
||||
},
|
||||
{
|
||||
"ATTACH USER user1 IDENTIFIED WITH sha256_hash BY '2CC4880302693485717D34E06046594CFDFE425E3F04AA5A094C4AABAB3CB0BF'", //for users created in older releases that sha256_password has no salt
|
||||
"^$"
|
||||
}
|
||||
})));
|
||||
|
Loading…
Reference in New Issue
Block a user