mirror of
https://github.com/ClickHouse/ClickHouse.git
synced 2024-09-20 00:30:49 +00:00
Merge pull request #65277 from arthurpassos/multi_auth_methods
Multi auth methods
This commit is contained in:
commit
7b94dc1813
@ -3150,3 +3150,15 @@ Default value: "default"
|
||||
|
||||
**See Also**
|
||||
- [Workload Scheduling](/docs/en/operations/workload-scheduling.md)
|
||||
|
||||
## max_authentication_methods_per_user {#max_authentication_methods_per_user}
|
||||
|
||||
The maximum number of authentication methods a user can be created with or altered to.
|
||||
Changing this setting does not affect existing users. Create/alter authentication-related queries will fail if they exceed the limit specified in this setting.
|
||||
Non authentication create/alter queries will succeed.
|
||||
|
||||
Type: UInt64
|
||||
|
||||
Default value: 100
|
||||
|
||||
Zero means unlimited
|
||||
|
@ -12,9 +12,10 @@ Syntax:
|
||||
``` sql
|
||||
ALTER USER [IF EXISTS] name1 [ON CLUSTER cluster_name1] [RENAME TO new_name1]
|
||||
[, name2 [ON CLUSTER cluster_name2] [RENAME TO new_name2] ...]
|
||||
[NOT IDENTIFIED | IDENTIFIED {[WITH {no_password | plaintext_password | sha256_password | sha256_hash | double_sha1_password | double_sha1_hash}] BY {'password' | 'hash'}} | {WITH ldap SERVER 'server_name'} | {WITH kerberos [REALM 'realm']} | {WITH ssl_certificate CN 'common_name' | SAN 'TYPE:subject_alt_name'}]
|
||||
[NOT IDENTIFIED | IDENTIFIED | ADD IDENTIFIED {[WITH {no_password | plaintext_password | sha256_password | sha256_hash | double_sha1_password | double_sha1_hash}] BY {'password' | 'hash'}} | {WITH ldap SERVER 'server_name'} | {WITH kerberos [REALM 'realm']} | {WITH ssl_certificate CN 'common_name' | SAN 'TYPE:subject_alt_name'}]
|
||||
[[ADD | DROP] HOST {LOCAL | NAME 'name' | REGEXP 'name_regexp' | IP 'address' | LIKE 'pattern'} [,...] | ANY | NONE]
|
||||
[VALID UNTIL datetime]
|
||||
[RESET AUTHENTICATION METHODS TO NEW]
|
||||
[DEFAULT ROLE role [,...] | ALL | ALL EXCEPT role [,...] ]
|
||||
[GRANTEES {user | role | ANY | NONE} [,...] [EXCEPT {user | role} [,...]]]
|
||||
[SETTINGS variable [= value] [MIN [=] min_value] [MAX [=] max_value] [READONLY | WRITABLE] | PROFILE 'profile_name'] [,...]
|
||||
@ -62,3 +63,31 @@ Allows the user with `john` account to grant his privileges to the user with `ja
|
||||
``` sql
|
||||
ALTER USER john GRANTEES jack;
|
||||
```
|
||||
|
||||
Adds new authentication methods to the user while keeping the existing ones:
|
||||
|
||||
``` sql
|
||||
ALTER USER user1 ADD IDENTIFIED WITH plaintext_password by '1', bcrypt_password by '2', plaintext_password by '3'
|
||||
```
|
||||
|
||||
Notes:
|
||||
1. Older versions of ClickHouse might not support the syntax of multiple authentication methods. Therefore, if the ClickHouse server contains such users and is downgraded to a version that does not support it, such users will become unusable and some user related operations will be broken. In order to downgrade gracefully, one must set all users to contain a single authentication method prior to downgrading. Alternatively, if the server was downgraded without the proper procedure, the faulty users should be dropped.
|
||||
2. `no_password` can not co-exist with other authentication methods for security reasons.
|
||||
Because of that, it is not possible to `ADD` a `no_password` authentication method. The below query will throw an error:
|
||||
|
||||
``` sql
|
||||
ALTER USER user1 ADD IDENTIFIED WITH no_password
|
||||
```
|
||||
|
||||
If you want to drop authentication methods for a user and rely on `no_password`, you must specify in the below replacing form.
|
||||
|
||||
Reset authentication methods and adds the ones specified in the query (effect of leading IDENTIFIED without the ADD keyword):
|
||||
|
||||
``` sql
|
||||
ALTER USER user1 IDENTIFIED WITH plaintext_password by '1', bcrypt_password by '2', plaintext_password by '3'
|
||||
```
|
||||
|
||||
Reset authentication methods and keep the most recent added one:
|
||||
``` sql
|
||||
ALTER USER user1 RESET AUTHENTICATION METHODS TO NEW
|
||||
```
|
||||
|
@ -15,6 +15,7 @@ CREATE USER [IF NOT EXISTS | OR REPLACE] name1 [ON CLUSTER cluster_name1]
|
||||
[NOT IDENTIFIED | IDENTIFIED {[WITH {no_password | plaintext_password | sha256_password | sha256_hash | double_sha1_password | double_sha1_hash}] BY {'password' | 'hash'}} | {WITH ldap SERVER 'server_name'} | {WITH kerberos [REALM 'realm']} | {WITH ssl_certificate CN 'common_name' | SAN 'TYPE:subject_alt_name'} | {WITH ssh_key BY KEY 'public_key' TYPE 'ssh-rsa|...'} | {WITH http SERVER 'server_name' [SCHEME 'Basic']}]
|
||||
[HOST {LOCAL | NAME 'name' | REGEXP 'name_regexp' | IP 'address' | LIKE 'pattern'} [,...] | ANY | NONE]
|
||||
[VALID UNTIL datetime]
|
||||
[RESET AUTHENTICATION METHODS TO NEW]
|
||||
[IN access_storage_type]
|
||||
[DEFAULT ROLE role [,...]]
|
||||
[DEFAULT DATABASE database | NONE]
|
||||
@ -144,6 +145,17 @@ In ClickHouse Cloud, by default, passwords must meet the following complexity re
|
||||
|
||||
The available password types are: `plaintext_password`, `sha256_password`, `double_sha1_password`.
|
||||
|
||||
7. Multiple authentication methods can be specified:
|
||||
|
||||
```sql
|
||||
CREATE USER user1 IDENTIFIED WITH plaintext_password by '1', bcrypt_password by '2', plaintext_password by '3''
|
||||
```
|
||||
|
||||
Notes:
|
||||
1. Older versions of ClickHouse might not support the syntax of multiple authentication methods. Therefore, if the ClickHouse server contains such users and is downgraded to a version that does not support it, such users will become unusable and some user related operations will be broken. In order to downgrade gracefully, one must set all users to contain a single authentication method prior to downgrading. Alternatively, if the server was downgraded without the proper procedure, the faulty users should be dropped.
|
||||
2. `no_password` can not co-exist with other authentication methods for security reasons. Therefore, you can only specify
|
||||
`no_password` if it is the only authentication method in the query.
|
||||
|
||||
## User Host
|
||||
|
||||
User host is a host from which a connection to ClickHouse server could be established. The host can be specified in the `HOST` query section in the following ways:
|
||||
|
@ -82,7 +82,7 @@ AccessEntityPtr deserializeAccessEntityImpl(const String & definition)
|
||||
if (res)
|
||||
throw Exception(ErrorCodes::INCORRECT_ACCESS_ENTITY_DEFINITION, "Two access entities attached in the same file");
|
||||
res = user = std::make_unique<User>();
|
||||
InterpreterCreateUserQuery::updateUserFromQuery(*user, *create_user_query, /* allow_no_password = */ true, /* allow_plaintext_password = */ true);
|
||||
InterpreterCreateUserQuery::updateUserFromQuery(*user, *create_user_query, /* allow_no_password = */ true, /* allow_plaintext_password = */ true, /* max_number_of_authentication_methods = zero is unlimited*/ 0);
|
||||
}
|
||||
else if (auto * create_role_query = query->as<ASTCreateRoleQuery>())
|
||||
{
|
||||
|
@ -14,11 +14,6 @@
|
||||
|
||||
namespace DB
|
||||
{
|
||||
namespace ErrorCodes
|
||||
{
|
||||
extern const int NOT_IMPLEMENTED;
|
||||
extern const int SUPPORT_IS_DISABLED;
|
||||
}
|
||||
|
||||
namespace
|
||||
{
|
||||
@ -84,12 +79,140 @@ namespace
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
bool checkKerberosAuthentication(
|
||||
const GSSAcceptorContext * gss_acceptor_context,
|
||||
const AuthenticationData & authentication_method,
|
||||
const ExternalAuthenticators & external_authenticators)
|
||||
{
|
||||
return authentication_method.getType() == AuthenticationType::KERBEROS
|
||||
&& external_authenticators.checkKerberosCredentials(authentication_method.getKerberosRealm(), *gss_acceptor_context);
|
||||
}
|
||||
|
||||
bool checkMySQLAuthentication(
|
||||
const MySQLNative41Credentials * mysql_credentials,
|
||||
const AuthenticationData & authentication_method)
|
||||
{
|
||||
switch (authentication_method.getType())
|
||||
{
|
||||
case AuthenticationType::PLAINTEXT_PASSWORD:
|
||||
return checkPasswordPlainTextMySQL(
|
||||
mysql_credentials->getScramble(),
|
||||
mysql_credentials->getScrambledPassword(),
|
||||
authentication_method.getPasswordHashBinary());
|
||||
case AuthenticationType::DOUBLE_SHA1_PASSWORD:
|
||||
return checkPasswordDoubleSHA1MySQL(
|
||||
mysql_credentials->getScramble(),
|
||||
mysql_credentials->getScrambledPassword(),
|
||||
authentication_method.getPasswordHashBinary());
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool checkBasicAuthentication(
|
||||
const BasicCredentials * basic_credentials,
|
||||
const AuthenticationData & authentication_method,
|
||||
const ExternalAuthenticators & external_authenticators,
|
||||
SettingsChanges & settings)
|
||||
{
|
||||
switch (authentication_method.getType())
|
||||
{
|
||||
case AuthenticationType::NO_PASSWORD:
|
||||
{
|
||||
return true; // N.B. even if the password is not empty!
|
||||
}
|
||||
case AuthenticationType::PLAINTEXT_PASSWORD:
|
||||
{
|
||||
return checkPasswordPlainText(basic_credentials->getPassword(), authentication_method.getPasswordHashBinary());
|
||||
}
|
||||
case AuthenticationType::SHA256_PASSWORD:
|
||||
{
|
||||
return checkPasswordSHA256(
|
||||
basic_credentials->getPassword(), authentication_method.getPasswordHashBinary(), authentication_method.getSalt());
|
||||
}
|
||||
case AuthenticationType::DOUBLE_SHA1_PASSWORD:
|
||||
{
|
||||
return checkPasswordDoubleSHA1(basic_credentials->getPassword(), authentication_method.getPasswordHashBinary());
|
||||
}
|
||||
case AuthenticationType::LDAP:
|
||||
{
|
||||
return external_authenticators.checkLDAPCredentials(authentication_method.getLDAPServerName(), *basic_credentials);
|
||||
}
|
||||
case AuthenticationType::BCRYPT_PASSWORD:
|
||||
{
|
||||
return checkPasswordBcrypt(basic_credentials->getPassword(), authentication_method.getPasswordHashBinary());
|
||||
}
|
||||
case AuthenticationType::HTTP:
|
||||
{
|
||||
if (authentication_method.getHTTPAuthenticationScheme() == HTTPAuthenticationScheme::BASIC)
|
||||
{
|
||||
return external_authenticators.checkHTTPBasicCredentials(
|
||||
authentication_method.getHTTPAuthenticationServerName(), *basic_credentials, settings);
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool checkSSLCertificateAuthentication(
|
||||
const SSLCertificateCredentials * ssl_certificate_credentials,
|
||||
const AuthenticationData & authentication_method)
|
||||
{
|
||||
if (AuthenticationType::SSL_CERTIFICATE != authentication_method.getType())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
for (SSLCertificateSubjects::Type type : {SSLCertificateSubjects::Type::CN, SSLCertificateSubjects::Type::SAN})
|
||||
{
|
||||
for (const auto & subject : authentication_method.getSSLCertificateSubjects().at(type))
|
||||
{
|
||||
if (ssl_certificate_credentials->getSSLCertificateSubjects().at(type).contains(subject))
|
||||
return true;
|
||||
|
||||
// Wildcard support (1 only)
|
||||
if (subject.contains('*'))
|
||||
{
|
||||
auto prefix = std::string_view(subject).substr(0, subject.find('*'));
|
||||
auto suffix = std::string_view(subject).substr(subject.find('*') + 1);
|
||||
auto slashes = std::count(subject.begin(), subject.end(), '/');
|
||||
|
||||
for (const auto & certificate_subject : ssl_certificate_credentials->getSSLCertificateSubjects().at(type))
|
||||
{
|
||||
bool matches_wildcard = certificate_subject.starts_with(prefix) && certificate_subject.ends_with(suffix);
|
||||
|
||||
// '*' must not represent a '/' in URI, so check if the number of '/' are equal
|
||||
bool matches_slashes = slashes == count(certificate_subject.begin(), certificate_subject.end(), '/');
|
||||
|
||||
if (matches_wildcard && matches_slashes)
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
#if USE_SSH
|
||||
bool checkSshAuthentication(
|
||||
const SshCredentials * ssh_credentials,
|
||||
const AuthenticationData & authentication_method)
|
||||
{
|
||||
return AuthenticationType::SSH_KEY == authentication_method.getType()
|
||||
&& checkSshSignature(authentication_method.getSSHKeys(), ssh_credentials->getSignature(), ssh_credentials->getOriginal());
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
bool Authentication::areCredentialsValid(
|
||||
const Credentials & credentials,
|
||||
const AuthenticationData & auth_data,
|
||||
const AuthenticationData & authentication_method,
|
||||
const ExternalAuthenticators & external_authenticators,
|
||||
SettingsChanges & settings)
|
||||
{
|
||||
@ -98,225 +221,35 @@ bool Authentication::areCredentialsValid(
|
||||
|
||||
if (const auto * gss_acceptor_context = typeid_cast<const GSSAcceptorContext *>(&credentials))
|
||||
{
|
||||
switch (auth_data.getType())
|
||||
{
|
||||
case AuthenticationType::NO_PASSWORD:
|
||||
case AuthenticationType::PLAINTEXT_PASSWORD:
|
||||
case AuthenticationType::SHA256_PASSWORD:
|
||||
case AuthenticationType::DOUBLE_SHA1_PASSWORD:
|
||||
case AuthenticationType::BCRYPT_PASSWORD:
|
||||
case AuthenticationType::LDAP:
|
||||
case AuthenticationType::HTTP:
|
||||
throw Authentication::Require<BasicCredentials>("ClickHouse Basic Authentication");
|
||||
|
||||
case AuthenticationType::JWT:
|
||||
throw Exception(ErrorCodes::SUPPORT_IS_DISABLED, "JWT is available only in ClickHouse Cloud");
|
||||
|
||||
case AuthenticationType::KERBEROS:
|
||||
return external_authenticators.checkKerberosCredentials(auth_data.getKerberosRealm(), *gss_acceptor_context);
|
||||
|
||||
case AuthenticationType::SSL_CERTIFICATE:
|
||||
throw Authentication::Require<BasicCredentials>("ClickHouse X.509 Authentication");
|
||||
|
||||
case AuthenticationType::SSH_KEY:
|
||||
#if USE_SSH
|
||||
throw Authentication::Require<SshCredentials>("SSH Keys Authentication");
|
||||
#else
|
||||
throw Exception(ErrorCodes::SUPPORT_IS_DISABLED, "SSH is disabled, because ClickHouse is built without libssh");
|
||||
#endif
|
||||
|
||||
case AuthenticationType::MAX:
|
||||
break;
|
||||
}
|
||||
return checkKerberosAuthentication(gss_acceptor_context, authentication_method, external_authenticators);
|
||||
}
|
||||
|
||||
if (const auto * mysql_credentials = typeid_cast<const MySQLNative41Credentials *>(&credentials))
|
||||
{
|
||||
switch (auth_data.getType())
|
||||
{
|
||||
case AuthenticationType::NO_PASSWORD:
|
||||
return true; // N.B. even if the password is not empty!
|
||||
|
||||
case AuthenticationType::PLAINTEXT_PASSWORD:
|
||||
return checkPasswordPlainTextMySQL(mysql_credentials->getScramble(), mysql_credentials->getScrambledPassword(), auth_data.getPasswordHashBinary());
|
||||
|
||||
case AuthenticationType::DOUBLE_SHA1_PASSWORD:
|
||||
return checkPasswordDoubleSHA1MySQL(mysql_credentials->getScramble(), mysql_credentials->getScrambledPassword(), auth_data.getPasswordHashBinary());
|
||||
|
||||
case AuthenticationType::SHA256_PASSWORD:
|
||||
case AuthenticationType::BCRYPT_PASSWORD:
|
||||
case AuthenticationType::LDAP:
|
||||
case AuthenticationType::KERBEROS:
|
||||
case AuthenticationType::HTTP:
|
||||
throw Authentication::Require<BasicCredentials>("ClickHouse Basic Authentication");
|
||||
|
||||
case AuthenticationType::SSL_CERTIFICATE:
|
||||
throw Authentication::Require<BasicCredentials>("ClickHouse X.509 Authentication");
|
||||
|
||||
case AuthenticationType::JWT:
|
||||
throw Exception(ErrorCodes::SUPPORT_IS_DISABLED, "JWT is available only in ClickHouse Cloud");
|
||||
|
||||
case AuthenticationType::SSH_KEY:
|
||||
#if USE_SSH
|
||||
throw Authentication::Require<SshCredentials>("SSH Keys Authentication");
|
||||
#else
|
||||
throw Exception(ErrorCodes::SUPPORT_IS_DISABLED, "SSH is disabled, because ClickHouse is built without libssh");
|
||||
#endif
|
||||
|
||||
case AuthenticationType::MAX:
|
||||
break;
|
||||
}
|
||||
return checkMySQLAuthentication(mysql_credentials, authentication_method);
|
||||
}
|
||||
|
||||
if (const auto * basic_credentials = typeid_cast<const BasicCredentials *>(&credentials))
|
||||
{
|
||||
switch (auth_data.getType())
|
||||
{
|
||||
case AuthenticationType::NO_PASSWORD:
|
||||
return true; // N.B. even if the password is not empty!
|
||||
|
||||
case AuthenticationType::PLAINTEXT_PASSWORD:
|
||||
return checkPasswordPlainText(basic_credentials->getPassword(), auth_data.getPasswordHashBinary());
|
||||
|
||||
case AuthenticationType::SHA256_PASSWORD:
|
||||
return checkPasswordSHA256(basic_credentials->getPassword(), auth_data.getPasswordHashBinary(), auth_data.getSalt());
|
||||
|
||||
case AuthenticationType::DOUBLE_SHA1_PASSWORD:
|
||||
return checkPasswordDoubleSHA1(basic_credentials->getPassword(), auth_data.getPasswordHashBinary());
|
||||
|
||||
case AuthenticationType::LDAP:
|
||||
return external_authenticators.checkLDAPCredentials(auth_data.getLDAPServerName(), *basic_credentials);
|
||||
|
||||
case AuthenticationType::KERBEROS:
|
||||
throw Authentication::Require<GSSAcceptorContext>(auth_data.getKerberosRealm());
|
||||
|
||||
case AuthenticationType::SSL_CERTIFICATE:
|
||||
throw Authentication::Require<BasicCredentials>("ClickHouse X.509 Authentication");
|
||||
|
||||
case AuthenticationType::SSH_KEY:
|
||||
#if USE_SSH
|
||||
throw Authentication::Require<SshCredentials>("SSH Keys Authentication");
|
||||
#else
|
||||
throw Exception(ErrorCodes::SUPPORT_IS_DISABLED, "SSH is disabled, because ClickHouse is built without libssh");
|
||||
#endif
|
||||
|
||||
case AuthenticationType::JWT:
|
||||
throw Exception(ErrorCodes::SUPPORT_IS_DISABLED, "JWT is available only in ClickHouse Cloud");
|
||||
|
||||
case AuthenticationType::BCRYPT_PASSWORD:
|
||||
return checkPasswordBcrypt(basic_credentials->getPassword(), auth_data.getPasswordHashBinary());
|
||||
|
||||
case AuthenticationType::HTTP:
|
||||
switch (auth_data.getHTTPAuthenticationScheme())
|
||||
{
|
||||
case HTTPAuthenticationScheme::BASIC:
|
||||
return external_authenticators.checkHTTPBasicCredentials(
|
||||
auth_data.getHTTPAuthenticationServerName(), *basic_credentials, settings);
|
||||
}
|
||||
|
||||
case AuthenticationType::MAX:
|
||||
break;
|
||||
}
|
||||
return checkBasicAuthentication(basic_credentials, authentication_method, external_authenticators, settings);
|
||||
}
|
||||
|
||||
if (const auto * ssl_certificate_credentials = typeid_cast<const SSLCertificateCredentials *>(&credentials))
|
||||
{
|
||||
switch (auth_data.getType())
|
||||
{
|
||||
case AuthenticationType::NO_PASSWORD:
|
||||
case AuthenticationType::PLAINTEXT_PASSWORD:
|
||||
case AuthenticationType::SHA256_PASSWORD:
|
||||
case AuthenticationType::DOUBLE_SHA1_PASSWORD:
|
||||
case AuthenticationType::BCRYPT_PASSWORD:
|
||||
case AuthenticationType::LDAP:
|
||||
case AuthenticationType::HTTP:
|
||||
throw Authentication::Require<BasicCredentials>("ClickHouse Basic Authentication");
|
||||
|
||||
case AuthenticationType::JWT:
|
||||
throw Exception(ErrorCodes::SUPPORT_IS_DISABLED, "JWT is available only in ClickHouse Cloud");
|
||||
|
||||
case AuthenticationType::KERBEROS:
|
||||
throw Authentication::Require<GSSAcceptorContext>(auth_data.getKerberosRealm());
|
||||
|
||||
case AuthenticationType::SSL_CERTIFICATE:
|
||||
{
|
||||
for (SSLCertificateSubjects::Type type : {SSLCertificateSubjects::Type::CN, SSLCertificateSubjects::Type::SAN})
|
||||
{
|
||||
for (const auto & subject : auth_data.getSSLCertificateSubjects().at(type))
|
||||
{
|
||||
if (ssl_certificate_credentials->getSSLCertificateSubjects().at(type).contains(subject))
|
||||
return true;
|
||||
|
||||
// Wildcard support (1 only)
|
||||
if (subject.contains('*'))
|
||||
{
|
||||
auto prefix = std::string_view(subject).substr(0, subject.find('*'));
|
||||
auto suffix = std::string_view(subject).substr(subject.find('*') + 1);
|
||||
auto slashes = std::count(subject.begin(), subject.end(), '/');
|
||||
|
||||
for (const auto & certificate_subject : ssl_certificate_credentials->getSSLCertificateSubjects().at(type))
|
||||
{
|
||||
bool matches_wildcard = certificate_subject.starts_with(prefix) && certificate_subject.ends_with(suffix);
|
||||
|
||||
// '*' must not represent a '/' in URI, so check if the number of '/' are equal
|
||||
bool matches_slashes = slashes == count(certificate_subject.begin(), certificate_subject.end(), '/');
|
||||
|
||||
if (matches_wildcard && matches_slashes)
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
case AuthenticationType::SSH_KEY:
|
||||
#if USE_SSH
|
||||
throw Authentication::Require<SshCredentials>("SSH Keys Authentication");
|
||||
#else
|
||||
throw Exception(ErrorCodes::SUPPORT_IS_DISABLED, "SSH is disabled, because ClickHouse is built without libssh");
|
||||
#endif
|
||||
|
||||
case AuthenticationType::MAX:
|
||||
break;
|
||||
}
|
||||
return checkSSLCertificateAuthentication(ssl_certificate_credentials, authentication_method);
|
||||
}
|
||||
|
||||
#if USE_SSH
|
||||
if (const auto * ssh_credentials = typeid_cast<const SshCredentials *>(&credentials))
|
||||
{
|
||||
switch (auth_data.getType())
|
||||
{
|
||||
case AuthenticationType::NO_PASSWORD:
|
||||
case AuthenticationType::PLAINTEXT_PASSWORD:
|
||||
case AuthenticationType::SHA256_PASSWORD:
|
||||
case AuthenticationType::DOUBLE_SHA1_PASSWORD:
|
||||
case AuthenticationType::BCRYPT_PASSWORD:
|
||||
case AuthenticationType::LDAP:
|
||||
case AuthenticationType::HTTP:
|
||||
throw Authentication::Require<BasicCredentials>("ClickHouse Basic Authentication");
|
||||
|
||||
case AuthenticationType::JWT:
|
||||
throw Exception(ErrorCodes::SUPPORT_IS_DISABLED, "JWT is available only in ClickHouse Cloud");
|
||||
|
||||
case AuthenticationType::KERBEROS:
|
||||
throw Authentication::Require<GSSAcceptorContext>(auth_data.getKerberosRealm());
|
||||
|
||||
case AuthenticationType::SSL_CERTIFICATE:
|
||||
throw Authentication::Require<SSLCertificateCredentials>("ClickHouse X.509 Authentication");
|
||||
|
||||
case AuthenticationType::SSH_KEY:
|
||||
return checkSshSignature(auth_data.getSSHKeys(), ssh_credentials->getSignature(), ssh_credentials->getOriginal());
|
||||
case AuthenticationType::MAX:
|
||||
break;
|
||||
}
|
||||
return checkSshAuthentication(ssh_credentials, authentication_method);
|
||||
}
|
||||
#endif
|
||||
|
||||
if ([[maybe_unused]] const auto * always_allow_credentials = typeid_cast<const AlwaysAllowCredentials *>(&credentials))
|
||||
return true;
|
||||
|
||||
throw Exception(ErrorCodes::NOT_IMPLEMENTED, "areCredentialsValid(): authentication type {} not supported", toString(auth_data.getType()));
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -24,7 +24,7 @@ struct Authentication
|
||||
/// returned by the authentication server
|
||||
static bool areCredentialsValid(
|
||||
const Credentials & credentials,
|
||||
const AuthenticationData & auth_data,
|
||||
const AuthenticationData & authentication_method,
|
||||
const ExternalAuthenticators & external_authenticators,
|
||||
SettingsChanges & settings);
|
||||
|
||||
|
@ -375,7 +375,8 @@ std::shared_ptr<ASTAuthenticationData> AuthenticationData::toAST() const
|
||||
break;
|
||||
}
|
||||
|
||||
case AuthenticationType::NO_PASSWORD: [[fallthrough]];
|
||||
case AuthenticationType::NO_PASSWORD:
|
||||
break;
|
||||
case AuthenticationType::MAX:
|
||||
throw Exception(ErrorCodes::LOGICAL_ERROR, "AST: Unexpected authentication type {}", toString(auth_type));
|
||||
}
|
||||
|
@ -30,7 +30,6 @@ namespace ErrorCodes
|
||||
extern const int IP_ADDRESS_NOT_ALLOWED;
|
||||
extern const int LOGICAL_ERROR;
|
||||
extern const int NOT_IMPLEMENTED;
|
||||
extern const int AUTHENTICATION_FAILED;
|
||||
}
|
||||
|
||||
|
||||
@ -525,15 +524,32 @@ std::optional<AuthResult> IAccessStorage::authenticateImpl(
|
||||
if (!isAddressAllowed(*user, address))
|
||||
throwAddressNotAllowed(address);
|
||||
|
||||
auto auth_type = user->auth_data.getType();
|
||||
if (((auth_type == AuthenticationType::NO_PASSWORD) && !allow_no_password) ||
|
||||
((auth_type == AuthenticationType::PLAINTEXT_PASSWORD) && !allow_plaintext_password))
|
||||
throwAuthenticationTypeNotAllowed(auth_type);
|
||||
bool skipped_not_allowed_authentication_methods = false;
|
||||
|
||||
if (!areCredentialsValid(*user, credentials, external_authenticators, auth_result.settings))
|
||||
throwInvalidCredentials();
|
||||
for (const auto & auth_method : user->authentication_methods)
|
||||
{
|
||||
auto auth_type = auth_method.getType();
|
||||
if (((auth_type == AuthenticationType::NO_PASSWORD) && !allow_no_password) ||
|
||||
((auth_type == AuthenticationType::PLAINTEXT_PASSWORD) && !allow_plaintext_password))
|
||||
{
|
||||
skipped_not_allowed_authentication_methods = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
return auth_result;
|
||||
if (areCredentialsValid(user->getName(), user->valid_until, auth_method, credentials, external_authenticators, auth_result.settings))
|
||||
{
|
||||
auth_result.authentication_data = auth_method;
|
||||
return auth_result;
|
||||
}
|
||||
}
|
||||
|
||||
if (skipped_not_allowed_authentication_methods)
|
||||
{
|
||||
LOG_INFO(log, "Skipped the check for not allowed authentication methods,"
|
||||
"check allow_no_password and allow_plaintext_password settings in the server configuration");
|
||||
}
|
||||
|
||||
throwInvalidCredentials();
|
||||
}
|
||||
}
|
||||
|
||||
@ -543,9 +559,10 @@ std::optional<AuthResult> IAccessStorage::authenticateImpl(
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
|
||||
bool IAccessStorage::areCredentialsValid(
|
||||
const User & user,
|
||||
const std::string & user_name,
|
||||
time_t valid_until,
|
||||
const AuthenticationData & authentication_method,
|
||||
const Credentials & credentials,
|
||||
const ExternalAuthenticators & external_authenticators,
|
||||
SettingsChanges & settings) const
|
||||
@ -553,21 +570,20 @@ bool IAccessStorage::areCredentialsValid(
|
||||
if (!credentials.isReady())
|
||||
return false;
|
||||
|
||||
if (credentials.getUserName() != user.getName())
|
||||
if (credentials.getUserName() != user_name)
|
||||
return false;
|
||||
|
||||
if (user.valid_until)
|
||||
if (valid_until)
|
||||
{
|
||||
const time_t now = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now());
|
||||
|
||||
if (now > user.valid_until)
|
||||
if (now > valid_until)
|
||||
return false;
|
||||
}
|
||||
|
||||
return Authentication::areCredentialsValid(credentials, user.auth_data, external_authenticators, settings);
|
||||
return Authentication::areCredentialsValid(credentials, authentication_method, external_authenticators, settings);
|
||||
}
|
||||
|
||||
|
||||
bool IAccessStorage::isAddressAllowed(const User & user, const Poco::Net::IPAddress & address) const
|
||||
{
|
||||
return user.allowed_client_hosts.contains(address);
|
||||
@ -747,14 +763,6 @@ void IAccessStorage::throwAddressNotAllowed(const Poco::Net::IPAddress & address
|
||||
throw Exception(ErrorCodes::IP_ADDRESS_NOT_ALLOWED, "Connections from {} are not allowed", address.toString());
|
||||
}
|
||||
|
||||
void IAccessStorage::throwAuthenticationTypeNotAllowed(AuthenticationType auth_type)
|
||||
{
|
||||
throw Exception(
|
||||
ErrorCodes::AUTHENTICATION_FAILED,
|
||||
"Authentication type {} is not allowed, check the setting allow_{} in the server configuration",
|
||||
toString(auth_type), AuthenticationTypeInfo::get(auth_type).name);
|
||||
}
|
||||
|
||||
void IAccessStorage::throwInvalidCredentials()
|
||||
{
|
||||
throw Exception(ErrorCodes::WRONG_PASSWORD, "Invalid credentials");
|
||||
|
@ -1,6 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <Access/IAccessEntity.h>
|
||||
#include <Access/AuthenticationData.h>
|
||||
#include <Core/Types.h>
|
||||
#include <Core/UUID.h>
|
||||
#include <Parsers/IParser.h>
|
||||
@ -34,6 +35,7 @@ struct AuthResult
|
||||
UUID user_id;
|
||||
/// Session settings received from authentication server (if any)
|
||||
SettingsChanges settings{};
|
||||
AuthenticationData authentication_data {};
|
||||
};
|
||||
|
||||
/// Contains entities, i.e. instances of classes derived from IAccessEntity.
|
||||
@ -227,7 +229,9 @@ protected:
|
||||
bool allow_no_password,
|
||||
bool allow_plaintext_password) const;
|
||||
virtual bool areCredentialsValid(
|
||||
const User & user,
|
||||
const std::string & user_name,
|
||||
time_t valid_until,
|
||||
const AuthenticationData & authentication_method,
|
||||
const Credentials & credentials,
|
||||
const ExternalAuthenticators & external_authenticators,
|
||||
SettingsChanges & settings) const;
|
||||
@ -248,7 +252,6 @@ protected:
|
||||
[[noreturn]] void throwReadonlyCannotRemove(AccessEntityType type, const String & name) const;
|
||||
[[noreturn]] static void throwAddressNotAllowed(const Poco::Net::IPAddress & address);
|
||||
[[noreturn]] static void throwInvalidCredentials();
|
||||
[[noreturn]] static void throwAuthenticationTypeNotAllowed(AuthenticationType auth_type);
|
||||
[[noreturn]] void throwBackupNotAllowed() const;
|
||||
[[noreturn]] void throwRestoreNotAllowed() const;
|
||||
|
||||
|
@ -468,8 +468,8 @@ std::optional<AuthResult> LDAPAccessStorage::authenticateImpl(
|
||||
// User does not exist, so we create one, and will add it if authentication is successful.
|
||||
new_user = std::make_shared<User>();
|
||||
new_user->setName(credentials.getUserName());
|
||||
new_user->auth_data = AuthenticationData(AuthenticationType::LDAP);
|
||||
new_user->auth_data.setLDAPServerName(ldap_server_name);
|
||||
new_user->authentication_methods.emplace_back(AuthenticationType::LDAP);
|
||||
new_user->authentication_methods.back().setLDAPServerName(ldap_server_name);
|
||||
user = new_user;
|
||||
}
|
||||
|
||||
@ -504,7 +504,7 @@ std::optional<AuthResult> LDAPAccessStorage::authenticateImpl(
|
||||
}
|
||||
|
||||
if (id)
|
||||
return AuthResult{ .user_id = *id };
|
||||
return AuthResult{ .user_id = *id, .authentication_data = AuthenticationData(AuthenticationType::LDAP) };
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
|
@ -16,7 +16,8 @@ bool User::equal(const IAccessEntity & other) const
|
||||
if (!IAccessEntity::equal(other))
|
||||
return false;
|
||||
const auto & other_user = typeid_cast<const User &>(other);
|
||||
return (auth_data == other_user.auth_data) && (allowed_client_hosts == other_user.allowed_client_hosts)
|
||||
return (authentication_methods == other_user.authentication_methods)
|
||||
&& (allowed_client_hosts == other_user.allowed_client_hosts)
|
||||
&& (access == other_user.access) && (granted_roles == other_user.granted_roles) && (default_roles == other_user.default_roles)
|
||||
&& (settings == other_user.settings) && (grantees == other_user.grantees) && (default_database == other_user.default_database)
|
||||
&& (valid_until == other_user.valid_until);
|
||||
|
@ -15,7 +15,7 @@ namespace DB
|
||||
*/
|
||||
struct User : public IAccessEntity
|
||||
{
|
||||
AuthenticationData auth_data;
|
||||
std::vector<AuthenticationData> authentication_methods;
|
||||
AllowedClientHosts allowed_client_hosts = AllowedClientHosts::AnyHostTag{};
|
||||
AccessRights access;
|
||||
GrantedRoles granted_roles;
|
||||
|
@ -155,18 +155,18 @@ namespace
|
||||
|
||||
if (has_password_plaintext)
|
||||
{
|
||||
user->auth_data = AuthenticationData{AuthenticationType::PLAINTEXT_PASSWORD};
|
||||
user->auth_data.setPassword(config.getString(user_config + ".password"));
|
||||
user->authentication_methods.emplace_back(AuthenticationType::PLAINTEXT_PASSWORD);
|
||||
user->authentication_methods.back().setPassword(config.getString(user_config + ".password"));
|
||||
}
|
||||
else if (has_password_sha256_hex)
|
||||
{
|
||||
user->auth_data = AuthenticationData{AuthenticationType::SHA256_PASSWORD};
|
||||
user->auth_data.setPasswordHashHex(config.getString(user_config + ".password_sha256_hex"));
|
||||
user->authentication_methods.emplace_back(AuthenticationType::SHA256_PASSWORD);
|
||||
user->authentication_methods.back().setPasswordHashHex(config.getString(user_config + ".password_sha256_hex"));
|
||||
}
|
||||
else if (has_password_double_sha1_hex)
|
||||
{
|
||||
user->auth_data = AuthenticationData{AuthenticationType::DOUBLE_SHA1_PASSWORD};
|
||||
user->auth_data.setPasswordHashHex(config.getString(user_config + ".password_double_sha1_hex"));
|
||||
user->authentication_methods.emplace_back(AuthenticationType::DOUBLE_SHA1_PASSWORD);
|
||||
user->authentication_methods.back().setPasswordHashHex(config.getString(user_config + ".password_double_sha1_hex"));
|
||||
}
|
||||
else if (has_ldap)
|
||||
{
|
||||
@ -178,19 +178,19 @@ namespace
|
||||
if (ldap_server_name.empty())
|
||||
throw Exception(ErrorCodes::BAD_ARGUMENTS, "LDAP server name cannot be empty for user {}.", user_name);
|
||||
|
||||
user->auth_data = AuthenticationData{AuthenticationType::LDAP};
|
||||
user->auth_data.setLDAPServerName(ldap_server_name);
|
||||
user->authentication_methods.emplace_back(AuthenticationType::LDAP);
|
||||
user->authentication_methods.back().setLDAPServerName(ldap_server_name);
|
||||
}
|
||||
else if (has_kerberos)
|
||||
{
|
||||
const auto realm = config.getString(user_config + ".kerberos.realm", "");
|
||||
|
||||
user->auth_data = AuthenticationData{AuthenticationType::KERBEROS};
|
||||
user->auth_data.setKerberosRealm(realm);
|
||||
user->authentication_methods.emplace_back(AuthenticationType::KERBEROS);
|
||||
user->authentication_methods.back().setKerberosRealm(realm);
|
||||
}
|
||||
else if (has_certificates)
|
||||
{
|
||||
user->auth_data = AuthenticationData{AuthenticationType::SSL_CERTIFICATE};
|
||||
user->authentication_methods.emplace_back(AuthenticationType::SSL_CERTIFICATE);
|
||||
|
||||
/// Fill list of allowed certificates.
|
||||
Poco::Util::AbstractConfiguration::Keys keys;
|
||||
@ -200,14 +200,14 @@ namespace
|
||||
if (key.starts_with("common_name"))
|
||||
{
|
||||
String value = config.getString(certificates_config + "." + key);
|
||||
user->auth_data.addSSLCertificateSubject(SSLCertificateSubjects::Type::CN, std::move(value));
|
||||
user->authentication_methods.back().addSSLCertificateSubject(SSLCertificateSubjects::Type::CN, std::move(value));
|
||||
}
|
||||
else if (key.starts_with("subject_alt_name"))
|
||||
{
|
||||
String value = config.getString(certificates_config + "." + key);
|
||||
if (value.empty())
|
||||
throw Exception(ErrorCodes::BAD_ARGUMENTS, "Expected ssl_certificates.subject_alt_name to not be empty");
|
||||
user->auth_data.addSSLCertificateSubject(SSLCertificateSubjects::Type::SAN, std::move(value));
|
||||
user->authentication_methods.back().addSSLCertificateSubject(SSLCertificateSubjects::Type::SAN, std::move(value));
|
||||
}
|
||||
else
|
||||
throw Exception(ErrorCodes::BAD_ARGUMENTS, "Unknown certificate pattern type: {}", key);
|
||||
@ -216,7 +216,7 @@ namespace
|
||||
else if (has_ssh_keys)
|
||||
{
|
||||
#if USE_SSH
|
||||
user->auth_data = AuthenticationData{AuthenticationType::SSH_KEY};
|
||||
user->authentication_methods.emplace_back(AuthenticationType::SSH_KEY);
|
||||
|
||||
Poco::Util::AbstractConfiguration::Keys entries;
|
||||
config.keys(ssh_keys_config, entries);
|
||||
@ -253,26 +253,33 @@ namespace
|
||||
else
|
||||
throw Exception(ErrorCodes::BAD_ARGUMENTS, "Unknown ssh_key entry pattern type: {}", entry);
|
||||
}
|
||||
user->auth_data.setSSHKeys(std::move(keys));
|
||||
user->authentication_methods.back().setSSHKeys(std::move(keys));
|
||||
#else
|
||||
throw Exception(ErrorCodes::SUPPORT_IS_DISABLED, "SSH is disabled, because ClickHouse is built without libssh");
|
||||
#endif
|
||||
}
|
||||
else if (has_http_auth)
|
||||
{
|
||||
user->auth_data = AuthenticationData{AuthenticationType::HTTP};
|
||||
user->auth_data.setHTTPAuthenticationServerName(config.getString(http_auth_config + ".server"));
|
||||
user->authentication_methods.emplace_back(AuthenticationType::HTTP);
|
||||
user->authentication_methods.back().setHTTPAuthenticationServerName(config.getString(http_auth_config + ".server"));
|
||||
auto scheme = config.getString(http_auth_config + ".scheme");
|
||||
user->auth_data.setHTTPAuthenticationScheme(parseHTTPAuthenticationScheme(scheme));
|
||||
user->authentication_methods.back().setHTTPAuthenticationScheme(parseHTTPAuthenticationScheme(scheme));
|
||||
}
|
||||
else
|
||||
{
|
||||
user->authentication_methods.emplace_back();
|
||||
}
|
||||
|
||||
auto auth_type = user->auth_data.getType();
|
||||
if (((auth_type == AuthenticationType::NO_PASSWORD) && !allow_no_password) ||
|
||||
((auth_type == AuthenticationType::PLAINTEXT_PASSWORD) && !allow_plaintext_password))
|
||||
for (const auto & authentication_method : user->authentication_methods)
|
||||
{
|
||||
throw Exception(ErrorCodes::BAD_ARGUMENTS,
|
||||
"Authentication type {} is not allowed, check the setting allow_{} in the server configuration",
|
||||
toString(auth_type), AuthenticationTypeInfo::get(auth_type).name);
|
||||
auto auth_type = authentication_method.getType();
|
||||
if (((auth_type == AuthenticationType::NO_PASSWORD) && !allow_no_password) ||
|
||||
((auth_type == AuthenticationType::PLAINTEXT_PASSWORD) && !allow_plaintext_password))
|
||||
{
|
||||
throw Exception(ErrorCodes::BAD_ARGUMENTS,
|
||||
"Authentication type {} is not allowed, check the setting allow_{} in the server configuration",
|
||||
toString(auth_type), AuthenticationTypeInfo::get(auth_type).name);
|
||||
}
|
||||
}
|
||||
|
||||
const auto profile_name_config = user_config + ".profile";
|
||||
|
@ -1875,11 +1875,11 @@ void ClientBase::processParsedSingleQuery(const String & full_query, const Strin
|
||||
|
||||
if (const auto * create_user_query = parsed_query->as<ASTCreateUserQuery>())
|
||||
{
|
||||
if (!create_user_query->attach && create_user_query->auth_data)
|
||||
if (!create_user_query->attach && !create_user_query->authentication_methods.empty())
|
||||
{
|
||||
if (const auto * auth_data = create_user_query->auth_data->as<ASTAuthenticationData>())
|
||||
for (const auto & authentication_method : create_user_query->authentication_methods)
|
||||
{
|
||||
auto password = auth_data->getPassword();
|
||||
auto password = authentication_method->getPassword();
|
||||
|
||||
if (password)
|
||||
client_context->getAccessControl().checkPasswordComplexityRules(*password);
|
||||
|
@ -890,16 +890,19 @@ public:
|
||||
Messaging::MessageTransport & mt,
|
||||
const Poco::Net::SocketAddress & address)
|
||||
{
|
||||
AuthenticationType user_auth_type;
|
||||
try
|
||||
{
|
||||
user_auth_type = session.getAuthenticationTypeOrLogInFailure(user_name);
|
||||
if (type_to_method.find(user_auth_type) != type_to_method.end())
|
||||
const auto user_authentication_types = session.getAuthenticationTypesOrLogInFailure(user_name);
|
||||
|
||||
for (auto user_authentication_type : user_authentication_types)
|
||||
{
|
||||
type_to_method[user_auth_type]->authenticate(user_name, session, mt, address);
|
||||
mt.send(Messaging::AuthenticationOk(), true);
|
||||
LOG_DEBUG(log, "Authentication for user {} was successful.", user_name);
|
||||
return;
|
||||
if (type_to_method.find(user_authentication_type) != type_to_method.end())
|
||||
{
|
||||
type_to_method[user_authentication_type]->authenticate(user_name, session, mt, address);
|
||||
mt.send(Messaging::AuthenticationOk(), true);
|
||||
LOG_DEBUG(log, "Authentication for user {} was successful.", user_name);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (const Exception&)
|
||||
@ -913,7 +916,7 @@ public:
|
||||
mt.send(Messaging::ErrorOrNoticeResponse(Messaging::ErrorOrNoticeResponse::ERROR, "0A000", "Authentication method is not supported"),
|
||||
true);
|
||||
|
||||
throw Exception(ErrorCodes::NOT_IMPLEMENTED, "Authentication method is not supported: {}", user_auth_type);
|
||||
throw Exception(ErrorCodes::NOT_IMPLEMENTED, "None of the authentication methods registered for the user are supported");
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@ -119,6 +119,7 @@ namespace DB
|
||||
M(UInt64, max_part_num_to_warn, 100000lu, "If the number of parts is greater than this value, the server will create a warning that will displayed to user.", 0) \
|
||||
M(UInt64, max_table_num_to_throw, 0lu, "If number of tables is greater than this value, server will throw an exception. 0 means no limitation. View, remote tables, dictionary, system tables are not counted. Only count table in Atomic/Ordinary/Replicated/Lazy database engine.", 0) \
|
||||
M(UInt64, max_database_num_to_throw, 0lu, "If number of databases is greater than this value, server will throw an exception. 0 means no limitation.", 0) \
|
||||
M(UInt64, max_authentication_methods_per_user, 100, "The maximum number of authentication methods a user can be created with or altered. Changing this setting does not affect existing users. Zero means unlimited", 0) \
|
||||
M(UInt64, concurrent_threads_soft_limit_num, 0, "Sets how many concurrent thread can be allocated before applying CPU pressure. Zero means unlimited.", 0) \
|
||||
M(UInt64, concurrent_threads_soft_limit_ratio_to_cores, 0, "Same as concurrent_threads_soft_limit_num, but with ratio to cores.", 0) \
|
||||
\
|
||||
|
@ -972,7 +972,6 @@ class IColumn;
|
||||
\
|
||||
M(Bool, allow_experimental_database_materialized_mysql, false, "Allow to create database with Engine=MaterializedMySQL(...).", 0) \
|
||||
M(Bool, allow_experimental_database_materialized_postgresql, false, "Allow to create database with Engine=MaterializedPostgreSQL(...).", 0) \
|
||||
\
|
||||
/** Experimental feature for moving data between shards. */ \
|
||||
M(Bool, allow_experimental_query_deduplication, false, "Experimental data deduplication for SELECT queries based on part UUIDs", 0) \
|
||||
|
||||
|
@ -6,6 +6,7 @@
|
||||
#include <Access/ReplicatedAccessStorage.h>
|
||||
#include <Access/User.h>
|
||||
#include <Common/logger_useful.h>
|
||||
#include <Core/ServerSettings.h>
|
||||
#include <Interpreters/Access/InterpreterSetRoleQuery.h>
|
||||
#include <Interpreters/Context.h>
|
||||
#include <Interpreters/executeDDLQueryOnCluster.h>
|
||||
@ -33,15 +34,18 @@ namespace
|
||||
void updateUserFromQueryImpl(
|
||||
User & user,
|
||||
const ASTCreateUserQuery & query,
|
||||
const std::optional<AuthenticationData> auth_data,
|
||||
const std::vector<AuthenticationData> authentication_methods,
|
||||
const std::shared_ptr<ASTUserNameWithHost> & override_name,
|
||||
const std::optional<RolesOrUsersSet> & override_default_roles,
|
||||
const std::optional<SettingsProfileElements> & override_settings,
|
||||
const std::optional<RolesOrUsersSet> & override_grantees,
|
||||
const std::optional<time_t> & valid_until,
|
||||
bool reset_authentication_methods,
|
||||
bool replace_authentication_methods,
|
||||
bool allow_implicit_no_password,
|
||||
bool allow_no_password,
|
||||
bool allow_plaintext_password)
|
||||
bool allow_plaintext_password,
|
||||
std::size_t max_number_of_authentication_methods)
|
||||
{
|
||||
if (override_name)
|
||||
user.setName(override_name->toString());
|
||||
@ -50,25 +54,77 @@ namespace
|
||||
else if (query.names->size() == 1)
|
||||
user.setName(query.names->front()->toString());
|
||||
|
||||
if (!query.attach && !query.alter && !auth_data && !allow_implicit_no_password)
|
||||
if (!query.attach && !query.alter && authentication_methods.empty() && !allow_implicit_no_password)
|
||||
throw Exception(ErrorCodes::BAD_ARGUMENTS,
|
||||
"Authentication type NO_PASSWORD must "
|
||||
"be explicitly specified, check the setting allow_implicit_no_password "
|
||||
"in the server configuration");
|
||||
|
||||
if (auth_data)
|
||||
user.auth_data = *auth_data;
|
||||
|
||||
if (auth_data || !query.alter)
|
||||
// if user does not have an authentication method and it has not been specified in the query,
|
||||
// add a default one
|
||||
if (user.authentication_methods.empty() && authentication_methods.empty())
|
||||
{
|
||||
auto auth_type = user.auth_data.getType();
|
||||
if (((auth_type == AuthenticationType::NO_PASSWORD) && !allow_no_password) ||
|
||||
((auth_type == AuthenticationType::PLAINTEXT_PASSWORD) && !allow_plaintext_password))
|
||||
user.authentication_methods.emplace_back();
|
||||
}
|
||||
|
||||
// 1. an IDENTIFIED WITH will drop existing authentication methods in favor of new ones.
|
||||
if (replace_authentication_methods)
|
||||
{
|
||||
user.authentication_methods.clear();
|
||||
}
|
||||
|
||||
// drop existing ones and keep the most recent
|
||||
if (reset_authentication_methods)
|
||||
{
|
||||
auto backup_authentication_method = user.authentication_methods.back();
|
||||
user.authentication_methods.clear();
|
||||
user.authentication_methods.emplace_back(backup_authentication_method);
|
||||
}
|
||||
|
||||
// max_number_of_authentication_methods == 0 means unlimited
|
||||
if (!authentication_methods.empty() && max_number_of_authentication_methods != 0)
|
||||
{
|
||||
// we only check if user exceeds the allowed quantity of authentication methods in case the create/alter query includes
|
||||
// authentication information. Otherwise, we can bypass this check to avoid blocking non-authentication related alters.
|
||||
auto number_of_authentication_methods = user.authentication_methods.size() + authentication_methods.size();
|
||||
if (number_of_authentication_methods > max_number_of_authentication_methods)
|
||||
{
|
||||
throw Exception(ErrorCodes::BAD_ARGUMENTS,
|
||||
"Authentication type {} is not allowed, check the setting allow_{} in the server configuration",
|
||||
toString(auth_type),
|
||||
AuthenticationTypeInfo::get(auth_type).name);
|
||||
"User can not be created/updated because it exceeds the allowed quantity of authentication methods per user. "
|
||||
"Check the `max_authentication_methods_per_user` setting");
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto & authentication_method : authentication_methods)
|
||||
{
|
||||
user.authentication_methods.emplace_back(authentication_method);
|
||||
}
|
||||
|
||||
bool has_no_password_authentication_method = std::find_if(user.authentication_methods.begin(),
|
||||
user.authentication_methods.end(),
|
||||
[](const AuthenticationData & authentication_data)
|
||||
{
|
||||
return authentication_data.getType() == AuthenticationType::NO_PASSWORD;
|
||||
}) != user.authentication_methods.end();
|
||||
|
||||
if (has_no_password_authentication_method && user.authentication_methods.size() > 1)
|
||||
{
|
||||
throw Exception(ErrorCodes::BAD_ARGUMENTS, "Authentication method 'no_password' cannot co-exist with other authentication methods");
|
||||
}
|
||||
|
||||
if (!query.alter)
|
||||
{
|
||||
for (const auto & authentication_method : user.authentication_methods)
|
||||
{
|
||||
auto auth_type = authentication_method.getType();
|
||||
if (((auth_type == AuthenticationType::NO_PASSWORD) && !allow_no_password) ||
|
||||
((auth_type == AuthenticationType::PLAINTEXT_PASSWORD) && !allow_plaintext_password))
|
||||
{
|
||||
throw Exception(ErrorCodes::BAD_ARGUMENTS,
|
||||
"Authentication type {} is not allowed, check the setting allow_{} in the server configuration",
|
||||
toString(auth_type),
|
||||
AuthenticationTypeInfo::get(auth_type).name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -156,9 +212,14 @@ BlockIO InterpreterCreateUserQuery::execute()
|
||||
bool no_password_allowed = access_control.isNoPasswordAllowed();
|
||||
bool plaintext_password_allowed = access_control.isPlaintextPasswordAllowed();
|
||||
|
||||
std::optional<AuthenticationData> auth_data;
|
||||
if (query.auth_data)
|
||||
auth_data = AuthenticationData::fromAST(*query.auth_data, getContext(), !query.attach);
|
||||
std::vector<AuthenticationData> authentication_methods;
|
||||
if (!query.authentication_methods.empty())
|
||||
{
|
||||
for (const auto & authentication_method_ast : query.authentication_methods)
|
||||
{
|
||||
authentication_methods.push_back(AuthenticationData::fromAST(*authentication_method_ast, getContext(), !query.attach));
|
||||
}
|
||||
}
|
||||
|
||||
std::optional<time_t> valid_until;
|
||||
if (query.valid_until)
|
||||
@ -207,8 +268,10 @@ BlockIO InterpreterCreateUserQuery::execute()
|
||||
{
|
||||
auto updated_user = typeid_cast<std::shared_ptr<User>>(entity->clone());
|
||||
updateUserFromQueryImpl(
|
||||
*updated_user, query, auth_data, {}, default_roles_from_query, settings_from_query, grantees_from_query,
|
||||
valid_until, implicit_no_password_allowed, no_password_allowed, plaintext_password_allowed);
|
||||
*updated_user, query, authentication_methods, {}, default_roles_from_query, settings_from_query, grantees_from_query,
|
||||
valid_until, query.reset_authentication_methods_to_new, query.replace_authentication_methods,
|
||||
implicit_no_password_allowed, no_password_allowed,
|
||||
plaintext_password_allowed, getContext()->getServerSettings().max_authentication_methods_per_user);
|
||||
return updated_user;
|
||||
};
|
||||
|
||||
@ -227,8 +290,10 @@ BlockIO InterpreterCreateUserQuery::execute()
|
||||
{
|
||||
auto new_user = std::make_shared<User>();
|
||||
updateUserFromQueryImpl(
|
||||
*new_user, query, auth_data, name, default_roles_from_query, settings_from_query, RolesOrUsersSet::AllTag{},
|
||||
valid_until, implicit_no_password_allowed, no_password_allowed, plaintext_password_allowed);
|
||||
*new_user, query, authentication_methods, name, default_roles_from_query, settings_from_query, RolesOrUsersSet::AllTag{},
|
||||
valid_until, query.reset_authentication_methods_to_new, query.replace_authentication_methods,
|
||||
implicit_no_password_allowed, no_password_allowed,
|
||||
plaintext_password_allowed, getContext()->getServerSettings().max_authentication_methods_per_user);
|
||||
new_users.emplace_back(std::move(new_user));
|
||||
}
|
||||
|
||||
@ -265,17 +330,41 @@ BlockIO InterpreterCreateUserQuery::execute()
|
||||
}
|
||||
|
||||
|
||||
void InterpreterCreateUserQuery::updateUserFromQuery(User & user, const ASTCreateUserQuery & query, bool allow_no_password, bool allow_plaintext_password)
|
||||
void InterpreterCreateUserQuery::updateUserFromQuery(
|
||||
User & user,
|
||||
const ASTCreateUserQuery & query,
|
||||
bool allow_no_password,
|
||||
bool allow_plaintext_password,
|
||||
std::size_t max_number_of_authentication_methods)
|
||||
{
|
||||
std::optional<AuthenticationData> auth_data;
|
||||
if (query.auth_data)
|
||||
auth_data = AuthenticationData::fromAST(*query.auth_data, {}, !query.attach);
|
||||
std::vector<AuthenticationData> authentication_methods;
|
||||
if (!query.authentication_methods.empty())
|
||||
{
|
||||
for (const auto & authentication_method_ast : query.authentication_methods)
|
||||
{
|
||||
authentication_methods.emplace_back(AuthenticationData::fromAST(*authentication_method_ast, {}, !query.attach));
|
||||
}
|
||||
}
|
||||
|
||||
std::optional<time_t> valid_until;
|
||||
if (query.valid_until)
|
||||
valid_until = getValidUntilFromAST(query.valid_until, {});
|
||||
|
||||
updateUserFromQueryImpl(user, query, auth_data, {}, {}, {}, {}, valid_until, allow_no_password, allow_plaintext_password, true);
|
||||
updateUserFromQueryImpl(
|
||||
user,
|
||||
query,
|
||||
authentication_methods,
|
||||
{},
|
||||
{},
|
||||
{},
|
||||
{},
|
||||
valid_until,
|
||||
query.reset_authentication_methods_to_new,
|
||||
query.replace_authentication_methods,
|
||||
allow_no_password,
|
||||
allow_plaintext_password,
|
||||
true,
|
||||
max_number_of_authentication_methods);
|
||||
}
|
||||
|
||||
void registerInterpreterCreateUserQuery(InterpreterFactory & factory)
|
||||
|
@ -17,7 +17,12 @@ public:
|
||||
|
||||
BlockIO execute() override;
|
||||
|
||||
static void updateUserFromQuery(User & user, const ASTCreateUserQuery & query, bool allow_no_password, bool allow_plaintext_password);
|
||||
static void updateUserFromQuery(
|
||||
User & user,
|
||||
const ASTCreateUserQuery & query,
|
||||
bool allow_no_password,
|
||||
bool allow_plaintext_password,
|
||||
std::size_t max_number_of_authentication_methods);
|
||||
|
||||
private:
|
||||
ASTPtr query_ptr;
|
||||
|
@ -64,8 +64,10 @@ namespace
|
||||
query->default_roles = user.default_roles.toASTWithNames(*access_control);
|
||||
}
|
||||
|
||||
if (user.auth_data.getType() != AuthenticationType::NO_PASSWORD)
|
||||
query->auth_data = user.auth_data.toAST();
|
||||
for (const auto & authentication_method : user.authentication_methods)
|
||||
{
|
||||
query->authentication_methods.push_back(authentication_method.toAST());
|
||||
}
|
||||
|
||||
if (user.valid_until)
|
||||
{
|
||||
|
@ -304,21 +304,30 @@ Session::~Session()
|
||||
LOG_DEBUG(log, "{} Logout, user_id: {}", toString(auth_id), toString(*user_id));
|
||||
if (auto session_log = getSessionLog())
|
||||
{
|
||||
session_log->addLogOut(auth_id, user, getClientInfo());
|
||||
session_log->addLogOut(auth_id, user, user_authenticated_with, getClientInfo());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
AuthenticationType Session::getAuthenticationType(const String & user_name) const
|
||||
std::unordered_set<AuthenticationType> Session::getAuthenticationTypes(const String & user_name) const
|
||||
{
|
||||
return global_context->getAccessControl().read<User>(user_name)->auth_data.getType();
|
||||
std::unordered_set<AuthenticationType> authentication_types;
|
||||
|
||||
const auto user_to_query = global_context->getAccessControl().read<User>(user_name);
|
||||
|
||||
for (const auto & authentication_method : user_to_query->authentication_methods)
|
||||
{
|
||||
authentication_types.insert(authentication_method.getType());
|
||||
}
|
||||
|
||||
return authentication_types;
|
||||
}
|
||||
|
||||
AuthenticationType Session::getAuthenticationTypeOrLogInFailure(const String & user_name) const
|
||||
std::unordered_set<AuthenticationType> Session::getAuthenticationTypesOrLogInFailure(const String & user_name) const
|
||||
{
|
||||
try
|
||||
{
|
||||
return getAuthenticationType(user_name);
|
||||
return getAuthenticationTypes(user_name);
|
||||
}
|
||||
catch (const Exception & e)
|
||||
{
|
||||
@ -354,6 +363,7 @@ void Session::authenticate(const Credentials & credentials_, const Poco::Net::So
|
||||
{
|
||||
auto auth_result = global_context->getAccessControl().authenticate(credentials_, address.host(), getClientInfo().getLastForwardedFor());
|
||||
user_id = auth_result.user_id;
|
||||
user_authenticated_with = auth_result.authentication_data;
|
||||
settings_from_auth_server = auth_result.settings;
|
||||
LOG_DEBUG(log, "{} Authenticated with global context as user {}",
|
||||
toString(auth_id), toString(*user_id));
|
||||
@ -698,7 +708,8 @@ void Session::recordLoginSuccess(ContextPtr login_context) const
|
||||
settings,
|
||||
access->getAccess(),
|
||||
getClientInfo(),
|
||||
user);
|
||||
user,
|
||||
user_authenticated_with);
|
||||
}
|
||||
|
||||
notified_session_log_about_login = true;
|
||||
|
@ -43,10 +43,10 @@ public:
|
||||
Session & operator=(const Session &) = delete;
|
||||
|
||||
/// Provides information about the authentication type of a specified user.
|
||||
AuthenticationType getAuthenticationType(const String & user_name) const;
|
||||
std::unordered_set<AuthenticationType> getAuthenticationTypes(const String & user_name) const;
|
||||
|
||||
/// Same as getAuthenticationType, but adds LoginFailure event in case of error.
|
||||
AuthenticationType getAuthenticationTypeOrLogInFailure(const String & user_name) const;
|
||||
std::unordered_set<AuthenticationType> getAuthenticationTypesOrLogInFailure(const String & user_name) const;
|
||||
|
||||
/// Sets the current user, checks the credentials and that the specified address is allowed to connect from.
|
||||
/// The function throws an exception if there is no such user or password is wrong.
|
||||
@ -113,6 +113,7 @@ private:
|
||||
|
||||
mutable UserPtr user;
|
||||
std::optional<UUID> user_id;
|
||||
AuthenticationData user_authenticated_with;
|
||||
|
||||
ContextMutablePtr session_context;
|
||||
mutable bool query_context_created = false;
|
||||
|
@ -214,7 +214,8 @@ void SessionLog::addLoginSuccess(const UUID & auth_id,
|
||||
const Settings & settings,
|
||||
const ContextAccessPtr & access,
|
||||
const ClientInfo & client_info,
|
||||
const UserPtr & login_user)
|
||||
const UserPtr & login_user,
|
||||
const AuthenticationData & user_authenticated_with)
|
||||
{
|
||||
SessionLogElement log_entry(auth_id, SESSION_LOGIN_SUCCESS);
|
||||
log_entry.client_info = client_info;
|
||||
@ -222,9 +223,11 @@ void SessionLog::addLoginSuccess(const UUID & auth_id,
|
||||
if (login_user)
|
||||
{
|
||||
log_entry.user = login_user->getName();
|
||||
log_entry.user_identified_with = login_user->auth_data.getType();
|
||||
log_entry.user_identified_with = user_authenticated_with.getType();
|
||||
}
|
||||
log_entry.external_auth_server = login_user ? login_user->auth_data.getLDAPServerName() : "";
|
||||
|
||||
log_entry.external_auth_server = user_authenticated_with.getLDAPServerName();
|
||||
|
||||
|
||||
log_entry.session_id = session_id;
|
||||
|
||||
@ -256,15 +259,19 @@ void SessionLog::addLoginFailure(
|
||||
add(std::move(log_entry));
|
||||
}
|
||||
|
||||
void SessionLog::addLogOut(const UUID & auth_id, const UserPtr & login_user, const ClientInfo & client_info)
|
||||
void SessionLog::addLogOut(
|
||||
const UUID & auth_id,
|
||||
const UserPtr & login_user,
|
||||
const AuthenticationData & user_authenticated_with,
|
||||
const ClientInfo & client_info)
|
||||
{
|
||||
auto log_entry = SessionLogElement(auth_id, SESSION_LOGOUT);
|
||||
if (login_user)
|
||||
{
|
||||
log_entry.user = login_user->getName();
|
||||
log_entry.user_identified_with = login_user->auth_data.getType();
|
||||
log_entry.user_identified_with = user_authenticated_with.getType();
|
||||
}
|
||||
log_entry.external_auth_server = login_user ? login_user->auth_data.getLDAPServerName() : "";
|
||||
log_entry.external_auth_server = user_authenticated_with.getLDAPServerName();
|
||||
log_entry.client_info = client_info;
|
||||
|
||||
add(std::move(log_entry));
|
||||
|
@ -22,6 +22,7 @@ class ContextAccess;
|
||||
struct User;
|
||||
using UserPtr = std::shared_ptr<const User>;
|
||||
using ContextAccessPtr = std::shared_ptr<const ContextAccess>;
|
||||
class AuthenticationData;
|
||||
|
||||
/** A struct which will be inserted as row into session_log table.
|
||||
*
|
||||
@ -71,17 +72,21 @@ struct SessionLogElement
|
||||
class SessionLog : public SystemLog<SessionLogElement>
|
||||
{
|
||||
using SystemLog<SessionLogElement>::SystemLog;
|
||||
|
||||
public:
|
||||
void addLoginSuccess(const UUID & auth_id,
|
||||
const String & session_id,
|
||||
const Settings & settings,
|
||||
const ContextAccessPtr & access,
|
||||
const ClientInfo & client_info,
|
||||
const UserPtr & login_user);
|
||||
const UserPtr & login_user,
|
||||
const AuthenticationData & user_authenticated_with);
|
||||
|
||||
void addLoginFailure(const UUID & auth_id, const ClientInfo & info, const std::optional<String> & user, const Exception & reason);
|
||||
void addLogOut(const UUID & auth_id, const UserPtr & login_user, const ClientInfo & client_info);
|
||||
void addLogOut(
|
||||
const UUID & auth_id,
|
||||
const UserPtr & login_user,
|
||||
const AuthenticationData & user_authenticated_with,
|
||||
const ClientInfo & client_info);
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -44,7 +44,7 @@ void ASTAuthenticationData::formatImpl(const FormatSettings & settings, FormatSt
|
||||
{
|
||||
if (type && *type == AuthenticationType::NO_PASSWORD)
|
||||
{
|
||||
settings.ostr << (settings.hilite ? IAST::hilite_keyword : "") << " NOT IDENTIFIED"
|
||||
settings.ostr << (settings.hilite ? IAST::hilite_keyword : "") << " no_password"
|
||||
<< (settings.hilite ? IAST::hilite_none : "");
|
||||
return;
|
||||
}
|
||||
@ -160,12 +160,9 @@ void ASTAuthenticationData::formatImpl(const FormatSettings & settings, FormatSt
|
||||
auth_type_name = AuthenticationTypeInfo::get(*type).name;
|
||||
}
|
||||
|
||||
settings.ostr << (settings.hilite ? IAST::hilite_keyword : "") << " IDENTIFIED" << (settings.hilite ? IAST::hilite_none : "");
|
||||
|
||||
if (!auth_type_name.empty())
|
||||
{
|
||||
settings.ostr << (settings.hilite ? IAST::hilite_keyword : "") << " WITH " << auth_type_name
|
||||
<< (settings.hilite ? IAST::hilite_none : "");
|
||||
settings.ostr << (settings.hilite ? IAST::hilite_keyword : "") << " " << auth_type_name << (settings.hilite ? IAST::hilite_none : "");
|
||||
}
|
||||
|
||||
if (!prefix.empty())
|
||||
|
@ -19,9 +19,25 @@ namespace
|
||||
<< quoteString(new_name);
|
||||
}
|
||||
|
||||
void formatAuthenticationData(const ASTAuthenticationData & auth_data, const IAST::FormatSettings & settings)
|
||||
void formatAuthenticationData(const std::vector<std::shared_ptr<ASTAuthenticationData>> & authentication_methods, const IAST::FormatSettings & settings)
|
||||
{
|
||||
auth_data.format(settings);
|
||||
// safe because this method is only called if authentication_methods.size > 1
|
||||
// if the first type is present, include the `WITH` keyword
|
||||
if (authentication_methods[0]->type)
|
||||
{
|
||||
settings.ostr << (settings.hilite ? IAST::hilite_keyword : "") << " WITH" << (settings.hilite ? IAST::hilite_none : "");
|
||||
}
|
||||
|
||||
for (std::size_t i = 0; i < authentication_methods.size(); i++)
|
||||
{
|
||||
authentication_methods[i]->format(settings);
|
||||
|
||||
bool is_last = i < authentication_methods.size() - 1;
|
||||
if (is_last)
|
||||
{
|
||||
settings.ostr << (settings.hilite ? IAST::hilite_keyword : ",");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void formatValidUntil(const IAST & valid_until, const IAST::FormatSettings & settings)
|
||||
@ -165,6 +181,7 @@ ASTPtr ASTCreateUserQuery::clone() const
|
||||
{
|
||||
auto res = std::make_shared<ASTCreateUserQuery>(*this);
|
||||
res->children.clear();
|
||||
res->authentication_methods.clear();
|
||||
|
||||
if (names)
|
||||
res->names = std::static_pointer_cast<ASTUserNamesWithHost>(names->clone());
|
||||
@ -181,10 +198,11 @@ ASTPtr ASTCreateUserQuery::clone() const
|
||||
if (settings)
|
||||
res->settings = std::static_pointer_cast<ASTSettingsProfileElements>(settings->clone());
|
||||
|
||||
if (auth_data)
|
||||
for (const auto & authentication_method : authentication_methods)
|
||||
{
|
||||
res->auth_data = std::static_pointer_cast<ASTAuthenticationData>(auth_data->clone());
|
||||
res->children.push_back(res->auth_data);
|
||||
auto ast_clone = std::static_pointer_cast<ASTAuthenticationData>(authentication_method->clone());
|
||||
res->authentication_methods.push_back(ast_clone);
|
||||
res->children.push_back(ast_clone);
|
||||
}
|
||||
|
||||
return res;
|
||||
@ -223,8 +241,24 @@ void ASTCreateUserQuery::formatImpl(const FormatSettings & format, FormatState &
|
||||
if (new_name)
|
||||
formatRenameTo(*new_name, format);
|
||||
|
||||
if (auth_data)
|
||||
formatAuthenticationData(*auth_data, format);
|
||||
if (authentication_methods.empty())
|
||||
{
|
||||
// If identification (auth method) is missing from query, we should serialize it in the form of `NO_PASSWORD` unless it is alter query
|
||||
if (!alter)
|
||||
{
|
||||
format.ostr << (format.hilite ? IAST::hilite_keyword : "") << " IDENTIFIED WITH no_password" << (format.hilite ? IAST::hilite_none : "");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (add_identified_with)
|
||||
{
|
||||
format.ostr << (format.hilite ? IAST::hilite_keyword : "") << " ADD" << (format.hilite ? IAST::hilite_none : "");
|
||||
}
|
||||
|
||||
format.ostr << (format.hilite ? IAST::hilite_keyword : "") << " IDENTIFIED" << (format.hilite ? IAST::hilite_none : "");
|
||||
formatAuthenticationData(authentication_methods, format);
|
||||
}
|
||||
|
||||
if (valid_until)
|
||||
formatValidUntil(*valid_until, format);
|
||||
@ -247,6 +281,9 @@ void ASTCreateUserQuery::formatImpl(const FormatSettings & format, FormatState &
|
||||
|
||||
if (grantees)
|
||||
formatGrantees(*grantees, format);
|
||||
|
||||
if (reset_authentication_methods_to_new)
|
||||
format.ostr << (format.hilite ? hilite_keyword : "") << " RESET AUTHENTICATION METHODS TO NEW" << (format.hilite ? hilite_none : "");
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -42,12 +42,15 @@ public:
|
||||
bool if_exists = false;
|
||||
bool if_not_exists = false;
|
||||
bool or_replace = false;
|
||||
bool reset_authentication_methods_to_new = false;
|
||||
bool add_identified_with = false;
|
||||
bool replace_authentication_methods = false;
|
||||
|
||||
std::shared_ptr<ASTUserNamesWithHost> names;
|
||||
std::optional<String> new_name;
|
||||
String storage_name;
|
||||
|
||||
std::shared_ptr<ASTAuthenticationData> auth_data;
|
||||
std::vector<std::shared_ptr<ASTAuthenticationData>> authentication_methods;
|
||||
|
||||
std::optional<AllowedClientHosts> hosts;
|
||||
std::optional<AllowedClientHosts> add_hosts;
|
||||
|
@ -43,21 +43,16 @@ namespace
|
||||
});
|
||||
}
|
||||
|
||||
bool parseAuthenticationData(IParserBase::Pos & pos, Expected & expected, std::shared_ptr<ASTAuthenticationData> & auth_data)
|
||||
bool parseAuthenticationData(
|
||||
IParserBase::Pos & pos,
|
||||
Expected & expected,
|
||||
std::shared_ptr<ASTAuthenticationData> & auth_data,
|
||||
bool is_type_specifier_mandatory,
|
||||
bool is_type_specifier_allowed,
|
||||
bool should_parse_no_password)
|
||||
{
|
||||
return IParserBase::wrapParseImpl(pos, [&]
|
||||
{
|
||||
if (ParserKeyword{Keyword::NOT_IDENTIFIED}.ignore(pos, expected))
|
||||
{
|
||||
auth_data = std::make_shared<ASTAuthenticationData>();
|
||||
auth_data->type = AuthenticationType::NO_PASSWORD;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!ParserKeyword{Keyword::IDENTIFIED}.ignore(pos, expected))
|
||||
return false;
|
||||
|
||||
std::optional<AuthenticationType> type;
|
||||
|
||||
bool expect_password = false;
|
||||
@ -68,51 +63,65 @@ namespace
|
||||
bool expect_public_ssh_key = false;
|
||||
bool expect_http_auth_server = false;
|
||||
|
||||
if (ParserKeyword{Keyword::WITH}.ignore(pos, expected))
|
||||
auto parse_non_password_based_type = [&](auto check_type)
|
||||
{
|
||||
for (auto check_type : collections::range(AuthenticationType::MAX))
|
||||
if (ParserKeyword{AuthenticationTypeInfo::get(check_type).keyword}.ignore(pos, expected))
|
||||
{
|
||||
if (ParserKeyword{AuthenticationTypeInfo::get(check_type).keyword}.ignore(pos, expected))
|
||||
{
|
||||
type = check_type;
|
||||
type = check_type;
|
||||
|
||||
if (check_type == AuthenticationType::LDAP)
|
||||
expect_ldap_server_name = true;
|
||||
else if (check_type == AuthenticationType::KERBEROS)
|
||||
expect_kerberos_realm = true;
|
||||
else if (check_type == AuthenticationType::SSL_CERTIFICATE)
|
||||
expect_ssl_cert_subjects = true;
|
||||
else if (check_type == AuthenticationType::SSH_KEY)
|
||||
expect_public_ssh_key = true;
|
||||
else if (check_type == AuthenticationType::HTTP)
|
||||
expect_http_auth_server = true;
|
||||
else if (check_type != AuthenticationType::NO_PASSWORD)
|
||||
expect_password = true;
|
||||
if (check_type == AuthenticationType::LDAP)
|
||||
expect_ldap_server_name = true;
|
||||
else if (check_type == AuthenticationType::KERBEROS)
|
||||
expect_kerberos_realm = true;
|
||||
else if (check_type == AuthenticationType::SSL_CERTIFICATE)
|
||||
expect_ssl_cert_subjects = true;
|
||||
else if (check_type == AuthenticationType::SSH_KEY)
|
||||
expect_public_ssh_key = true;
|
||||
else if (check_type == AuthenticationType::HTTP)
|
||||
expect_http_auth_server = true;
|
||||
else if (check_type != AuthenticationType::NO_PASSWORD)
|
||||
expect_password = true;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
{
|
||||
const auto first_authentication_type_element_to_check
|
||||
= should_parse_no_password ? AuthenticationType::NO_PASSWORD : AuthenticationType::PLAINTEXT_PASSWORD;
|
||||
|
||||
for (auto check_type : collections::range(first_authentication_type_element_to_check, AuthenticationType::MAX))
|
||||
{
|
||||
if (parse_non_password_based_type(check_type))
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!type)
|
||||
if (!type)
|
||||
{
|
||||
if (ParserKeyword{Keyword::SHA256_HASH}.ignore(pos, expected))
|
||||
{
|
||||
if (ParserKeyword{Keyword::SHA256_HASH}.ignore(pos, expected))
|
||||
{
|
||||
type = AuthenticationType::SHA256_PASSWORD;
|
||||
expect_hash = true;
|
||||
}
|
||||
else if (ParserKeyword{Keyword::DOUBLE_SHA1_HASH}.ignore(pos, expected))
|
||||
{
|
||||
type = AuthenticationType::DOUBLE_SHA1_PASSWORD;
|
||||
expect_hash = true;
|
||||
}
|
||||
else if (ParserKeyword{Keyword::BCRYPT_HASH}.ignore(pos, expected))
|
||||
{
|
||||
type = AuthenticationType::BCRYPT_PASSWORD;
|
||||
expect_hash = true;
|
||||
}
|
||||
else
|
||||
return false;
|
||||
type = AuthenticationType::SHA256_PASSWORD;
|
||||
expect_hash = true;
|
||||
}
|
||||
else if (ParserKeyword{Keyword::DOUBLE_SHA1_HASH}.ignore(pos, expected))
|
||||
{
|
||||
type = AuthenticationType::DOUBLE_SHA1_PASSWORD;
|
||||
expect_hash = true;
|
||||
}
|
||||
else if (ParserKeyword{Keyword::BCRYPT_HASH}.ignore(pos, expected))
|
||||
{
|
||||
type = AuthenticationType::BCRYPT_PASSWORD;
|
||||
expect_hash = true;
|
||||
}
|
||||
else if (is_type_specifier_mandatory)
|
||||
return false;
|
||||
}
|
||||
else if (!is_type_specifier_allowed)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/// If authentication type is not specified, then the default password type is used
|
||||
@ -219,6 +228,69 @@ namespace
|
||||
}
|
||||
|
||||
|
||||
bool parseIdentifiedWith(
|
||||
IParserBase::Pos & pos,
|
||||
Expected & expected,
|
||||
std::vector<std::shared_ptr<ASTAuthenticationData>> & authentication_methods,
|
||||
bool should_parse_no_password)
|
||||
{
|
||||
return IParserBase::wrapParseImpl(pos, [&]
|
||||
{
|
||||
if (!ParserKeyword{Keyword::IDENTIFIED}.ignore(pos, expected))
|
||||
return false;
|
||||
|
||||
// Parse first authentication method which doesn't come with a leading comma
|
||||
{
|
||||
bool is_type_specifier_mandatory = ParserKeyword{Keyword::WITH}.ignore(pos, expected);
|
||||
|
||||
std::shared_ptr<ASTAuthenticationData> ast_authentication_data;
|
||||
|
||||
if (!parseAuthenticationData(pos, expected, ast_authentication_data, is_type_specifier_mandatory, is_type_specifier_mandatory, should_parse_no_password))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
authentication_methods.push_back(ast_authentication_data);
|
||||
}
|
||||
|
||||
// Need to save current position, process comma and only update real position in case there is an authentication method after
|
||||
// the comma. Otherwise, position should not be changed as it needs to be processed by other parsers and possibly throw error
|
||||
// on trailing comma.
|
||||
IParserBase::Pos aux_pos = pos;
|
||||
while (ParserToken{TokenType::Comma}.ignore(aux_pos, expected))
|
||||
{
|
||||
std::shared_ptr<ASTAuthenticationData> ast_authentication_data;
|
||||
|
||||
if (!parseAuthenticationData(aux_pos, expected, ast_authentication_data, false, true, should_parse_no_password))
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
pos = aux_pos;
|
||||
authentication_methods.push_back(ast_authentication_data);
|
||||
}
|
||||
|
||||
return !authentication_methods.empty();
|
||||
});
|
||||
}
|
||||
|
||||
bool parseIdentifiedOrNotIdentified(IParserBase::Pos & pos, Expected & expected, std::vector<std::shared_ptr<ASTAuthenticationData>> & authentication_methods)
|
||||
{
|
||||
return IParserBase::wrapParseImpl(pos, [&]
|
||||
{
|
||||
if (ParserKeyword{Keyword::NOT_IDENTIFIED}.ignore(pos, expected))
|
||||
{
|
||||
authentication_methods.emplace_back(std::make_shared<ASTAuthenticationData>());
|
||||
authentication_methods.back()->type = AuthenticationType::NO_PASSWORD;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return parseIdentifiedWith(pos, expected, authentication_methods, true);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
bool parseHostsWithoutPrefix(IParserBase::Pos & pos, Expected & expected, AllowedClientHosts & hosts)
|
||||
{
|
||||
AllowedClientHosts res_hosts;
|
||||
@ -411,6 +483,27 @@ namespace
|
||||
return until_p.parse(pos, valid_until, expected);
|
||||
});
|
||||
}
|
||||
|
||||
bool parseAddIdentifiedWith(IParserBase::Pos & pos, Expected & expected, std::vector<std::shared_ptr<ASTAuthenticationData>> & auth_data)
|
||||
{
|
||||
return IParserBase::wrapParseImpl(pos, [&]
|
||||
{
|
||||
if (!ParserKeyword{Keyword::ADD}.ignore(pos, expected))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return parseIdentifiedWith(pos, expected, auth_data, false);
|
||||
});
|
||||
}
|
||||
|
||||
bool parseResetAuthenticationMethods(IParserBase::Pos & pos, Expected & expected)
|
||||
{
|
||||
return IParserBase::wrapParseImpl(pos, [&]
|
||||
{
|
||||
return ParserKeyword{Keyword::RESET_AUTHENTICATION_METHODS_TO_NEW}.ignore(pos, expected);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -456,7 +549,7 @@ bool ParserCreateUserQuery::parseImpl(Pos & pos, ASTPtr & node, Expected & expec
|
||||
std::optional<AllowedClientHosts> hosts;
|
||||
std::optional<AllowedClientHosts> add_hosts;
|
||||
std::optional<AllowedClientHosts> remove_hosts;
|
||||
std::shared_ptr<ASTAuthenticationData> auth_data;
|
||||
std::vector<std::shared_ptr<ASTAuthenticationData>> auth_data;
|
||||
std::shared_ptr<ASTRolesOrUsersSet> default_roles;
|
||||
std::shared_ptr<ASTSettingsProfileElements> settings;
|
||||
std::shared_ptr<ASTRolesOrUsersSet> grantees;
|
||||
@ -464,19 +557,28 @@ bool ParserCreateUserQuery::parseImpl(Pos & pos, ASTPtr & node, Expected & expec
|
||||
ASTPtr valid_until;
|
||||
String cluster;
|
||||
String storage_name;
|
||||
bool reset_authentication_methods_to_new = false;
|
||||
|
||||
bool parsed_identified_with = false;
|
||||
bool parsed_add_identified_with = false;
|
||||
|
||||
while (true)
|
||||
{
|
||||
if (!auth_data)
|
||||
if (auth_data.empty() && !reset_authentication_methods_to_new)
|
||||
{
|
||||
std::shared_ptr<ASTAuthenticationData> new_auth_data;
|
||||
if (parseAuthenticationData(pos, expected, new_auth_data))
|
||||
parsed_identified_with = parseIdentifiedOrNotIdentified(pos, expected, auth_data);
|
||||
|
||||
if (!parsed_identified_with && alter)
|
||||
{
|
||||
auth_data = std::move(new_auth_data);
|
||||
continue;
|
||||
parsed_add_identified_with = parseAddIdentifiedWith(pos, expected, auth_data);
|
||||
}
|
||||
}
|
||||
|
||||
if (!reset_authentication_methods_to_new && alter && auth_data.empty())
|
||||
{
|
||||
reset_authentication_methods_to_new = parseResetAuthenticationMethods(pos, expected);
|
||||
}
|
||||
|
||||
if (!valid_until)
|
||||
{
|
||||
parseValidUntil(pos, expected, valid_until);
|
||||
@ -564,7 +666,7 @@ bool ParserCreateUserQuery::parseImpl(Pos & pos, ASTPtr & node, Expected & expec
|
||||
query->cluster = std::move(cluster);
|
||||
query->names = std::move(names);
|
||||
query->new_name = std::move(new_name);
|
||||
query->auth_data = std::move(auth_data);
|
||||
query->authentication_methods = std::move(auth_data);
|
||||
query->hosts = std::move(hosts);
|
||||
query->add_hosts = std::move(add_hosts);
|
||||
query->remove_hosts = std::move(remove_hosts);
|
||||
@ -574,9 +676,14 @@ bool ParserCreateUserQuery::parseImpl(Pos & pos, ASTPtr & node, Expected & expec
|
||||
query->default_database = std::move(default_database);
|
||||
query->valid_until = std::move(valid_until);
|
||||
query->storage_name = std::move(storage_name);
|
||||
query->reset_authentication_methods_to_new = reset_authentication_methods_to_new;
|
||||
query->add_identified_with = parsed_add_identified_with;
|
||||
query->replace_authentication_methods = parsed_identified_with;
|
||||
|
||||
if (query->auth_data)
|
||||
query->children.push_back(query->auth_data);
|
||||
for (const auto & authentication_method : query->authentication_methods)
|
||||
{
|
||||
query->children.push_back(authentication_method);
|
||||
}
|
||||
|
||||
if (query->valid_until)
|
||||
query->children.push_back(query->valid_until);
|
||||
|
@ -407,6 +407,7 @@ namespace DB
|
||||
MR_MACROS(REPLACE_PARTITION, "REPLACE PARTITION") \
|
||||
MR_MACROS(REPLACE, "REPLACE") \
|
||||
MR_MACROS(RESET_SETTING, "RESET SETTING") \
|
||||
MR_MACROS(RESET_AUTHENTICATION_METHODS_TO_NEW, "RESET AUTHENTICATION METHODS TO NEW") \
|
||||
MR_MACROS(RESPECT_NULLS, "RESPECT NULLS") \
|
||||
MR_MACROS(RESTORE, "RESTORE") \
|
||||
MR_MACROS(RESTRICT, "RESTRICT") \
|
||||
|
@ -87,7 +87,7 @@ TEST_P(ParserTest, parseQuery)
|
||||
{
|
||||
if (input_text.starts_with("ATTACH"))
|
||||
{
|
||||
auto salt = (dynamic_cast<const ASTCreateUserQuery *>(ast.get())->auth_data)->getSalt().value_or("");
|
||||
auto salt = (dynamic_cast<const ASTCreateUserQuery *>(ast.get())->authentication_methods.back())->getSalt().value_or("");
|
||||
EXPECT_TRUE(re2::RE2::FullMatch(salt, expected_ast));
|
||||
}
|
||||
else
|
||||
@ -283,6 +283,18 @@ INSTANTIATE_TEST_SUITE_P(ParserCreateUserQuery, ParserTest,
|
||||
"CREATE USER user1 IDENTIFIED WITH sha256_password BY 'qwe123'",
|
||||
"CREATE USER user1 IDENTIFIED WITH sha256_password BY 'qwe123'"
|
||||
},
|
||||
{
|
||||
"CREATE USER user1 IDENTIFIED WITH no_password",
|
||||
"CREATE USER user1 IDENTIFIED WITH no_password"
|
||||
},
|
||||
{
|
||||
"CREATE USER user1",
|
||||
"CREATE USER user1 IDENTIFIED WITH no_password"
|
||||
},
|
||||
{
|
||||
"CREATE USER user1 IDENTIFIED WITH plaintext_password BY 'abc123', plaintext_password BY 'def123', sha256_password BY 'ghi123'",
|
||||
"CREATE USER user1 IDENTIFIED WITH plaintext_password BY 'abc123', plaintext_password BY 'def123', sha256_password BY 'ghi123'"
|
||||
},
|
||||
{
|
||||
"CREATE USER user1 IDENTIFIED WITH sha256_hash BY '7A37B85C8918EAC19A9089C0FA5A2AB4DCE3F90528DCDEEC108B23DDF3607B99' SALT 'salt'",
|
||||
"CREATE USER user1 IDENTIFIED WITH sha256_hash BY '7A37B85C8918EAC19A9089C0FA5A2AB4DCE3F90528DCDEEC108B23DDF3607B99' SALT 'salt'"
|
||||
@ -291,6 +303,10 @@ INSTANTIATE_TEST_SUITE_P(ParserCreateUserQuery, ParserTest,
|
||||
"ALTER USER user1 IDENTIFIED WITH sha256_password BY 'qwe123'",
|
||||
"ALTER USER user1 IDENTIFIED WITH sha256_password BY 'qwe123'"
|
||||
},
|
||||
{
|
||||
"ALTER USER user1 IDENTIFIED WITH plaintext_password BY 'abc123', plaintext_password BY 'def123', sha256_password BY 'ghi123'",
|
||||
"ALTER USER user1 IDENTIFIED WITH plaintext_password BY 'abc123', plaintext_password BY 'def123', sha256_password BY 'ghi123'"
|
||||
},
|
||||
{
|
||||
"ALTER USER user1 IDENTIFIED WITH sha256_hash BY '7A37B85C8918EAC19A9089C0FA5A2AB4DCE3F90528DCDEEC108B23DDF3607B99' SALT 'salt'",
|
||||
"ALTER USER user1 IDENTIFIED WITH sha256_hash BY '7A37B85C8918EAC19A9089C0FA5A2AB4DCE3F90528DCDEEC108B23DDF3607B99' SALT 'salt'"
|
||||
@ -298,6 +314,10 @@ INSTANTIATE_TEST_SUITE_P(ParserCreateUserQuery, ParserTest,
|
||||
{
|
||||
"CREATE USER user1 IDENTIFIED WITH sha256_password BY 'qwe123' SALT 'EFFD7F6B03B3EA68B8F86C1E91614DD50E42EB31EF7160524916444D58B5E264'",
|
||||
"throws Syntax error"
|
||||
},
|
||||
{
|
||||
"ALTER USER user1 IDENTIFIED WITH plaintext_password BY 'abc123' IDENTIFIED WITH plaintext_password BY 'def123'",
|
||||
"throws Only one identified with is permitted"
|
||||
}
|
||||
})));
|
||||
|
||||
|
@ -63,7 +63,7 @@ TEST_P(ParserKQLTest, parseKQLQuery)
|
||||
{
|
||||
if (input_text.starts_with("ATTACH"))
|
||||
{
|
||||
auto salt = (dynamic_cast<const ASTCreateUserQuery *>(ast.get())->auth_data)->getSalt().value_or("");
|
||||
auto salt = (dynamic_cast<const ASTCreateUserQuery *>(ast.get())->authentication_methods.back())->getSalt().value_or("");
|
||||
EXPECT_TRUE(re2::RE2::FullMatch(salt, expected_ast));
|
||||
}
|
||||
else
|
||||
|
@ -376,11 +376,16 @@ void MySQLHandler::authenticate(const String & user_name, const String & auth_pl
|
||||
{
|
||||
try
|
||||
{
|
||||
// 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.
|
||||
if (session->getAuthenticationTypeOrLogInFailure(user_name) == DB::AuthenticationType::SHA256_PASSWORD)
|
||||
const auto user_authentication_types = session->getAuthenticationTypesOrLogInFailure(user_name);
|
||||
|
||||
for (const auto user_authentication_type : user_authentication_types)
|
||||
{
|
||||
authPluginSSL();
|
||||
// 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.
|
||||
if (user_authentication_type == DB::AuthenticationType::SHA256_PASSWORD)
|
||||
{
|
||||
authPluginSSL();
|
||||
}
|
||||
}
|
||||
|
||||
std::optional<String> auth_response = auth_plugin_name == auth_plugin->getName() ? std::make_optional<String>(initial_auth_response) : std::nullopt;
|
||||
|
@ -1592,7 +1592,17 @@ void TCPHandler::receiveHello()
|
||||
/// Perform handshake for SSH authentication
|
||||
if (is_ssh_based_auth)
|
||||
{
|
||||
if (session->getAuthenticationTypeOrLogInFailure(user) != AuthenticationType::SSH_KEY)
|
||||
const auto authentication_types = session->getAuthenticationTypesOrLogInFailure(user);
|
||||
|
||||
bool user_supports_ssh_authentication = std::find_if(
|
||||
authentication_types.begin(),
|
||||
authentication_types.end(),
|
||||
[](auto authentication_type)
|
||||
{
|
||||
return authentication_type == AuthenticationType::SSH_KEY;
|
||||
}) != authentication_types.end();
|
||||
|
||||
if (!user_supports_ssh_authentication)
|
||||
throw Exception(ErrorCodes::AUTHENTICATION_FAILED, "Expected authentication with SSH key");
|
||||
|
||||
if (client_tcp_protocol_version < DBMS_MIN_REVISION_WITH_SSH_AUTHENTICATION)
|
||||
|
@ -16,6 +16,7 @@
|
||||
#include <Parsers/Access/ASTRolesOrUsersSet.h>
|
||||
#include <Poco/JSON/JSON.h>
|
||||
#include <Poco/JSON/Object.h>
|
||||
#include <Poco/JSON/Array.h>
|
||||
#include <Poco/JSON/Stringifier.h>
|
||||
#include <Poco/JSONString.h>
|
||||
|
||||
@ -48,13 +49,15 @@ ColumnsDescription StorageSystemUsers::getColumnsDescription()
|
||||
{"name", std::make_shared<DataTypeString>(), "User name."},
|
||||
{"id", std::make_shared<DataTypeUUID>(), "User ID."},
|
||||
{"storage", std::make_shared<DataTypeString>(), "Path to the storage of users. Configured in the access_control_path parameter."},
|
||||
{"auth_type", std::make_shared<DataTypeEnum8>(getAuthenticationTypeEnumValues()),
|
||||
"Shows the authentication type. "
|
||||
{"auth_type", std::make_shared<DataTypeArray>(std::make_shared<DataTypeEnum8>(getAuthenticationTypeEnumValues())),
|
||||
"Shows the authentication types. "
|
||||
"There are multiple ways of user identification: "
|
||||
"with no password, with plain text password, with SHA256-encoded password, "
|
||||
"with double SHA-1-encoded password or with bcrypt-encoded password."
|
||||
},
|
||||
{"auth_params", std::make_shared<DataTypeString>(), "Authentication parameters in the JSON format depending on the auth_type."},
|
||||
{"auth_params", std::make_shared<DataTypeArray>(std::make_shared<DataTypeString>()),
|
||||
"Authentication parameters in the JSON format depending on the auth_type."
|
||||
},
|
||||
{"host_ip", std::make_shared<DataTypeArray>(std::make_shared<DataTypeString>()),
|
||||
"IP addresses of hosts that are allowed to connect to the ClickHouse server."
|
||||
},
|
||||
@ -97,8 +100,10 @@ void StorageSystemUsers::fillData(MutableColumns & res_columns, ContextPtr conte
|
||||
auto & column_name = assert_cast<ColumnString &>(*res_columns[column_index++]);
|
||||
auto & column_id = assert_cast<ColumnUUID &>(*res_columns[column_index++]).getData();
|
||||
auto & column_storage = assert_cast<ColumnString &>(*res_columns[column_index++]);
|
||||
auto & column_auth_type = assert_cast<ColumnInt8 &>(*res_columns[column_index++]).getData();
|
||||
auto & column_auth_params = assert_cast<ColumnString &>(*res_columns[column_index++]);
|
||||
auto & column_auth_type = assert_cast<ColumnInt8 &>(assert_cast<ColumnArray &>(*res_columns[column_index]).getData());
|
||||
auto & column_auth_type_offsets = assert_cast<ColumnArray &>(*res_columns[column_index++]).getOffsets();
|
||||
auto & column_auth_params = assert_cast<ColumnString &>(assert_cast<ColumnArray &>(*res_columns[column_index]).getData());
|
||||
auto & column_auth_params_offsets = assert_cast<ColumnArray &>(*res_columns[column_index++]).getOffsets();
|
||||
auto & column_host_ip = assert_cast<ColumnString &>(assert_cast<ColumnArray &>(*res_columns[column_index]).getData());
|
||||
auto & column_host_ip_offsets = assert_cast<ColumnArray &>(*res_columns[column_index++]).getOffsets();
|
||||
auto & column_host_names = assert_cast<ColumnString &>(assert_cast<ColumnArray &>(*res_columns[column_index]).getData());
|
||||
@ -122,7 +127,7 @@ void StorageSystemUsers::fillData(MutableColumns & res_columns, ContextPtr conte
|
||||
auto add_row = [&](const String & name,
|
||||
const UUID & id,
|
||||
const String & storage_name,
|
||||
const AuthenticationData & auth_data,
|
||||
const std::vector<AuthenticationData> & authentication_methods,
|
||||
const AllowedClientHosts & allowed_hosts,
|
||||
const RolesOrUsersSet & default_roles,
|
||||
const RolesOrUsersSet & grantees,
|
||||
@ -131,11 +136,8 @@ void StorageSystemUsers::fillData(MutableColumns & res_columns, ContextPtr conte
|
||||
column_name.insertData(name.data(), name.length());
|
||||
column_id.push_back(id.toUnderType());
|
||||
column_storage.insertData(storage_name.data(), storage_name.length());
|
||||
column_auth_type.push_back(static_cast<Int8>(auth_data.getType()));
|
||||
|
||||
if (auth_data.getType() == AuthenticationType::LDAP ||
|
||||
auth_data.getType() == AuthenticationType::KERBEROS ||
|
||||
auth_data.getType() == AuthenticationType::SSL_CERTIFICATE)
|
||||
for (const auto & auth_data : authentication_methods)
|
||||
{
|
||||
Poco::JSON::Object auth_params_json;
|
||||
|
||||
@ -167,16 +169,15 @@ void StorageSystemUsers::fillData(MutableColumns & res_columns, ContextPtr conte
|
||||
std::ostringstream oss; // STYLE_CHECK_ALLOW_STD_STRING_STREAM
|
||||
oss.exceptions(std::ios::failbit);
|
||||
Poco::JSON::Stringifier::stringify(auth_params_json, oss);
|
||||
const auto str = oss.str();
|
||||
const auto authentication_params_str = oss.str();
|
||||
|
||||
column_auth_params.insertData(str.data(), str.size());
|
||||
}
|
||||
else
|
||||
{
|
||||
static constexpr std::string_view empty_json{"{}"};
|
||||
column_auth_params.insertData(empty_json.data(), empty_json.length());
|
||||
column_auth_params.insertData(authentication_params_str.data(), authentication_params_str.size());
|
||||
column_auth_type.insertValue(static_cast<Int8>(auth_data.getType()));
|
||||
}
|
||||
|
||||
column_auth_params_offsets.push_back(column_auth_params.size());
|
||||
column_auth_type_offsets.push_back(column_auth_type.size());
|
||||
|
||||
if (allowed_hosts.containsAnyHost())
|
||||
{
|
||||
static constexpr std::string_view str{"::/0"};
|
||||
@ -247,7 +248,7 @@ void StorageSystemUsers::fillData(MutableColumns & res_columns, ContextPtr conte
|
||||
if (!storage)
|
||||
continue;
|
||||
|
||||
add_row(user->getName(), id, storage->getStorageName(), user->auth_data, user->allowed_client_hosts,
|
||||
add_row(user->getName(), id, storage->getStorageName(), user->authentication_methods, user->allowed_client_hosts,
|
||||
user->default_roles, user->grantees, user->default_database);
|
||||
}
|
||||
}
|
||||
|
@ -42,9 +42,18 @@ def test_access_control_on_cluster():
|
||||
ch1.query_with_retry(
|
||||
"CREATE USER IF NOT EXISTS Alex ON CLUSTER 'cluster'", retry_count=5
|
||||
)
|
||||
assert ch1.query("SHOW CREATE USER Alex") == "CREATE USER Alex\n"
|
||||
assert ch2.query("SHOW CREATE USER Alex") == "CREATE USER Alex\n"
|
||||
assert ch3.query("SHOW CREATE USER Alex") == "CREATE USER Alex\n"
|
||||
assert (
|
||||
ch2.query("SHOW CREATE USER Alex")
|
||||
== "CREATE USER Alex IDENTIFIED WITH no_password\n"
|
||||
)
|
||||
assert (
|
||||
ch1.query("SHOW CREATE USER Alex")
|
||||
== "CREATE USER Alex IDENTIFIED WITH no_password\n"
|
||||
)
|
||||
assert (
|
||||
ch3.query("SHOW CREATE USER Alex")
|
||||
== "CREATE USER Alex IDENTIFIED WITH no_password\n"
|
||||
)
|
||||
|
||||
ch2.query_with_retry(
|
||||
"GRANT ON CLUSTER 'cluster' SELECT ON *.* TO Alex", retry_count=3
|
||||
|
@ -1236,7 +1236,10 @@ def test_system_users_required_privileges():
|
||||
instance.query("GRANT SELECT ON test.* TO u2 WITH GRANT OPTION")
|
||||
instance.query(f"RESTORE ALL FROM {backup_name}", user="u2")
|
||||
|
||||
assert instance.query("SHOW CREATE USER u1") == "CREATE USER u1 DEFAULT ROLE r1\n"
|
||||
assert (
|
||||
instance.query("SHOW CREATE USER u1")
|
||||
== "CREATE USER u1 IDENTIFIED WITH no_password DEFAULT ROLE r1\n"
|
||||
)
|
||||
assert instance.query("SHOW GRANTS FOR u1") == TSV(
|
||||
["GRANT SELECT ON test.* TO u1", "GRANT r1 TO u1"]
|
||||
)
|
||||
|
@ -769,7 +769,8 @@ def test_system_users():
|
||||
)
|
||||
|
||||
assert (
|
||||
node1.query("SHOW CREATE USER u1") == "CREATE USER u1 SETTINGS custom_a = 123\n"
|
||||
node1.query("SHOW CREATE USER u1")
|
||||
== "CREATE USER u1 IDENTIFIED WITH no_password SETTINGS custom_a = 123\n"
|
||||
)
|
||||
assert node1.query("SHOW GRANTS FOR u1") == "GRANT SELECT ON default.tbl TO u1\n"
|
||||
|
||||
|
@ -46,7 +46,7 @@ def test_create():
|
||||
def check():
|
||||
assert (
|
||||
instance.query("SHOW CREATE USER u1")
|
||||
== "CREATE USER u1 SETTINGS PROFILE `s1`\n"
|
||||
== "CREATE USER u1 IDENTIFIED WITH no_password SETTINGS PROFILE `s1`\n"
|
||||
)
|
||||
assert (
|
||||
instance.query("SHOW CREATE USER u2")
|
||||
@ -99,7 +99,7 @@ def test_alter():
|
||||
def check():
|
||||
assert (
|
||||
instance.query("SHOW CREATE USER u1")
|
||||
== "CREATE USER u1 SETTINGS PROFILE `s1`\n"
|
||||
== "CREATE USER u1 IDENTIFIED WITH no_password SETTINGS PROFILE `s1`\n"
|
||||
)
|
||||
assert (
|
||||
instance.query("SHOW CREATE USER u2")
|
||||
@ -147,7 +147,10 @@ def test_drop():
|
||||
instance.query("DROP SETTINGS PROFILE s1")
|
||||
|
||||
def check():
|
||||
assert instance.query("SHOW CREATE USER u1") == "CREATE USER u1\n"
|
||||
assert (
|
||||
instance.query("SHOW CREATE USER u1")
|
||||
== "CREATE USER u1 IDENTIFIED WITH no_password\n"
|
||||
)
|
||||
assert (
|
||||
instance.query("SHOW CREATE SETTINGS PROFILE s2")
|
||||
== "CREATE SETTINGS PROFILE `s2`\n"
|
||||
|
@ -18,12 +18,16 @@ def started_cluster():
|
||||
|
||||
|
||||
def test_enabling_access_management():
|
||||
instance.query("DROP USER IF EXISTS Alex")
|
||||
|
||||
instance.query("CREATE USER Alex", user="default")
|
||||
assert (
|
||||
instance.query("SHOW CREATE USER Alex", user="default") == "CREATE USER Alex\n"
|
||||
instance.query("SHOW CREATE USER Alex", user="default")
|
||||
== "CREATE USER Alex IDENTIFIED WITH no_password\n"
|
||||
)
|
||||
assert (
|
||||
instance.query("SHOW CREATE USER Alex", user="readonly") == "CREATE USER Alex\n"
|
||||
instance.query("SHOW CREATE USER Alex", user="readonly")
|
||||
== "CREATE USER Alex IDENTIFIED WITH no_password\n"
|
||||
)
|
||||
assert "Not enough privileges" in instance.query_and_get_error(
|
||||
"SHOW CREATE USER Alex", user="xyz"
|
||||
@ -35,3 +39,5 @@ def test_enabling_access_management():
|
||||
assert "Not enough privileges" in instance.query_and_get_error(
|
||||
"CREATE USER Robin", user="xyz"
|
||||
)
|
||||
|
||||
instance.query("DROP USER IF EXISTS Alex")
|
||||
|
@ -36,7 +36,8 @@ def cleanup_after_test():
|
||||
yield
|
||||
finally:
|
||||
instance.query("DROP USER IF EXISTS A, B, C")
|
||||
instance.query("DROP TABLE IF EXISTS test.view_1")
|
||||
|
||||
instance.query("DROP TABLE IF EXISTS test.view_1, test.view_2, default.table")
|
||||
|
||||
|
||||
def test_smoke():
|
||||
@ -144,7 +145,8 @@ def test_allowed_grantees():
|
||||
|
||||
instance.query("ALTER USER A GRANTEES ANY EXCEPT B")
|
||||
assert (
|
||||
instance.query("SHOW CREATE USER A") == "CREATE USER A GRANTEES ANY EXCEPT B\n"
|
||||
instance.query("SHOW CREATE USER A")
|
||||
== "CREATE USER A IDENTIFIED WITH no_password GRANTEES ANY EXCEPT B\n"
|
||||
)
|
||||
expected_error = "user `B` is not allowed as grantee"
|
||||
assert expected_error in instance.query_and_get_error(
|
||||
@ -157,7 +159,10 @@ def test_allowed_grantees():
|
||||
instance.query("REVOKE SELECT ON test.table FROM B", user="A")
|
||||
|
||||
instance.query("ALTER USER A GRANTEES ANY")
|
||||
assert instance.query("SHOW CREATE USER A") == "CREATE USER A\n"
|
||||
assert (
|
||||
instance.query("SHOW CREATE USER A")
|
||||
== "CREATE USER A IDENTIFIED WITH no_password\n"
|
||||
)
|
||||
instance.query("GRANT SELECT ON test.table TO B", user="A")
|
||||
assert instance.query("SELECT * FROM test.table", user="B") == "1\t5\n2\t10\n"
|
||||
|
||||
@ -169,7 +174,8 @@ def test_allowed_grantees():
|
||||
|
||||
instance.query("CREATE USER C GRANTEES ANY EXCEPT C")
|
||||
assert (
|
||||
instance.query("SHOW CREATE USER C") == "CREATE USER C GRANTEES ANY EXCEPT C\n"
|
||||
instance.query("SHOW CREATE USER C")
|
||||
== "CREATE USER C IDENTIFIED WITH no_password GRANTEES ANY EXCEPT C\n"
|
||||
)
|
||||
instance.query("GRANT SELECT ON test.table TO C WITH GRANT OPTION")
|
||||
assert instance.query("SELECT * FROM test.table", user="C") == "1\t5\n2\t10\n"
|
||||
@ -387,15 +393,22 @@ def test_introspection():
|
||||
instance.query("GRANT CREATE ON *.* TO B WITH GRANT OPTION")
|
||||
|
||||
assert instance.query("SHOW USERS") == TSV(["A", "B", "default"])
|
||||
assert instance.query("SHOW CREATE USERS A") == TSV(["CREATE USER A"])
|
||||
assert instance.query("SHOW CREATE USERS B") == TSV(["CREATE USER B"])
|
||||
assert instance.query("SHOW CREATE USERS A") == TSV(
|
||||
["CREATE USER A IDENTIFIED WITH no_password"]
|
||||
)
|
||||
assert instance.query("SHOW CREATE USERS B") == TSV(
|
||||
["CREATE USER B IDENTIFIED WITH no_password"]
|
||||
)
|
||||
assert instance.query("SHOW CREATE USERS A,B") == TSV(
|
||||
["CREATE USER A", "CREATE USER B"]
|
||||
[
|
||||
"CREATE USER A IDENTIFIED WITH no_password",
|
||||
"CREATE USER B IDENTIFIED WITH no_password",
|
||||
]
|
||||
)
|
||||
assert instance.query("SHOW CREATE USERS") == TSV(
|
||||
[
|
||||
"CREATE USER A",
|
||||
"CREATE USER B",
|
||||
"CREATE USER A IDENTIFIED WITH no_password",
|
||||
"CREATE USER B IDENTIFIED WITH no_password",
|
||||
"CREATE USER default IDENTIFIED WITH plaintext_password SETTINGS PROFILE `default`",
|
||||
]
|
||||
)
|
||||
@ -454,8 +467,8 @@ def test_introspection():
|
||||
assert expected_error in instance.query_and_get_error("SHOW GRANTS FOR B", user="A")
|
||||
|
||||
expected_access1 = (
|
||||
"CREATE USER A\n"
|
||||
"CREATE USER B\n"
|
||||
"CREATE USER A IDENTIFIED WITH no_password\n"
|
||||
"CREATE USER B IDENTIFIED WITH no_password\n"
|
||||
"CREATE USER default IDENTIFIED WITH plaintext_password SETTINGS PROFILE `default`"
|
||||
)
|
||||
expected_access2 = (
|
||||
@ -473,8 +486,8 @@ def test_introspection():
|
||||
[
|
||||
"A",
|
||||
"local_directory",
|
||||
"no_password",
|
||||
"{}",
|
||||
"['no_password']",
|
||||
"['{}']",
|
||||
"['::/0']",
|
||||
"[]",
|
||||
"[]",
|
||||
@ -486,8 +499,8 @@ def test_introspection():
|
||||
[
|
||||
"B",
|
||||
"local_directory",
|
||||
"no_password",
|
||||
"{}",
|
||||
"['no_password']",
|
||||
"['{}']",
|
||||
"['::/0']",
|
||||
"[]",
|
||||
"[]",
|
||||
|
@ -0,0 +1,3 @@
|
||||
<clickhouse>
|
||||
<max_authentication_methods_per_user>2</max_authentication_methods_per_user>
|
||||
</clickhouse>
|
@ -0,0 +1,126 @@
|
||||
import pytest
|
||||
from helpers.cluster import ClickHouseCluster
|
||||
from helpers.client import QueryRuntimeException
|
||||
|
||||
cluster = ClickHouseCluster(__file__)
|
||||
|
||||
limited_node = cluster.add_instance(
|
||||
"limited_node",
|
||||
main_configs=["configs/max_auth_limited.xml"],
|
||||
)
|
||||
|
||||
default_node = cluster.add_instance(
|
||||
"default_node",
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def started_cluster():
|
||||
try:
|
||||
cluster.start()
|
||||
yield cluster
|
||||
|
||||
finally:
|
||||
cluster.shutdown()
|
||||
|
||||
|
||||
expected_error = "User can not be created/updated because it exceeds the allowed quantity of authentication methods per user"
|
||||
|
||||
|
||||
def test_create(started_cluster):
|
||||
|
||||
assert expected_error in limited_node.query_and_get_error(
|
||||
"CREATE USER u_max_authentication_methods IDENTIFIED BY '1', BY '2', BY '3'"
|
||||
)
|
||||
|
||||
assert expected_error not in limited_node.query_and_get_answer_with_error(
|
||||
"CREATE USER u_max_authentication_methods IDENTIFIED BY '1', BY '2'"
|
||||
)
|
||||
|
||||
limited_node.query("DROP USER u_max_authentication_methods")
|
||||
|
||||
|
||||
def test_alter(started_cluster):
|
||||
limited_node.query("CREATE USER u_max_authentication_methods IDENTIFIED BY '1'")
|
||||
|
||||
assert expected_error in limited_node.query_and_get_error(
|
||||
"ALTER USER u_max_authentication_methods ADD IDENTIFIED BY '2', BY '3'"
|
||||
)
|
||||
|
||||
assert expected_error in limited_node.query_and_get_error(
|
||||
"ALTER USER u_max_authentication_methods IDENTIFIED BY '3', BY '4', BY '5'"
|
||||
)
|
||||
|
||||
assert expected_error not in limited_node.query_and_get_answer_with_error(
|
||||
"ALTER USER u_max_authentication_methods ADD IDENTIFIED BY '2'"
|
||||
)
|
||||
|
||||
assert expected_error not in limited_node.query_and_get_answer_with_error(
|
||||
"ALTER USER u_max_authentication_methods IDENTIFIED BY '2', BY '3'"
|
||||
)
|
||||
|
||||
limited_node.query("DROP USER u_max_authentication_methods")
|
||||
|
||||
|
||||
def get_query_with_multiple_identified_with(
|
||||
operation, username, identified_with_count, add_operation=""
|
||||
):
|
||||
identified_clauses = ", ".join([f"BY '1'" for _ in range(identified_with_count)])
|
||||
query = (
|
||||
f"{operation} USER {username} {add_operation} IDENTIFIED {identified_clauses}"
|
||||
)
|
||||
return query
|
||||
|
||||
|
||||
def test_create_default_setting(started_cluster):
|
||||
expected_error = "User can not be created/updated because it exceeds the allowed quantity of authentication methods per user"
|
||||
|
||||
query_exceeds = get_query_with_multiple_identified_with(
|
||||
"CREATE", "u_max_authentication_methods", 101
|
||||
)
|
||||
|
||||
assert expected_error in default_node.query_and_get_error(query_exceeds)
|
||||
|
||||
query_not_exceeds = get_query_with_multiple_identified_with(
|
||||
"CREATE", "u_max_authentication_methods", 100
|
||||
)
|
||||
|
||||
assert expected_error not in default_node.query_and_get_answer_with_error(
|
||||
query_not_exceeds
|
||||
)
|
||||
|
||||
default_node.query("DROP USER u_max_authentication_methods")
|
||||
|
||||
|
||||
def test_alter_default_setting(started_cluster):
|
||||
default_node.query("CREATE USER u_max_authentication_methods IDENTIFIED BY '1'")
|
||||
|
||||
query_add_exceeds = get_query_with_multiple_identified_with(
|
||||
"ALTER", "u_max_authentication_methods", 100, "ADD"
|
||||
)
|
||||
|
||||
assert expected_error in default_node.query_and_get_error(query_add_exceeds)
|
||||
|
||||
query_replace_exceeds = get_query_with_multiple_identified_with(
|
||||
"ALTER", "u_max_authentication_methods", 101
|
||||
)
|
||||
|
||||
assert expected_error in default_node.query_and_get_error(query_replace_exceeds)
|
||||
|
||||
query_add_not_exceeds = get_query_with_multiple_identified_with(
|
||||
"ALTER", "u_max_authentication_methods", 99, "ADD"
|
||||
)
|
||||
|
||||
assert expected_error not in default_node.query_and_get_answer_with_error(
|
||||
query_add_not_exceeds
|
||||
)
|
||||
|
||||
query_replace_not_exceeds = get_query_with_multiple_identified_with(
|
||||
"ALTER", "u_max_authentication_methods", 100
|
||||
)
|
||||
|
||||
assert expected_error not in default_node.query_and_get_answer_with_error(
|
||||
query_replace_not_exceeds
|
||||
)
|
||||
|
||||
default_node.query("DROP USER u_max_authentication_methods")
|
@ -128,7 +128,7 @@ def test_smoke():
|
||||
instance.query("ALTER USER robin SETTINGS PROFILE xyz")
|
||||
assert (
|
||||
instance.query("SHOW CREATE USER robin")
|
||||
== "CREATE USER robin SETTINGS PROFILE `xyz`\n"
|
||||
== "CREATE USER robin IDENTIFIED WITH no_password SETTINGS PROFILE `xyz`\n"
|
||||
)
|
||||
assert (
|
||||
instance.query(
|
||||
@ -152,7 +152,10 @@ def test_smoke():
|
||||
]
|
||||
|
||||
instance.query("ALTER USER robin SETTINGS NONE")
|
||||
assert instance.query("SHOW CREATE USER robin") == "CREATE USER robin\n"
|
||||
assert (
|
||||
instance.query("SHOW CREATE USER robin")
|
||||
== "CREATE USER robin IDENTIFIED WITH no_password\n"
|
||||
)
|
||||
assert (
|
||||
instance.query(
|
||||
"SELECT value FROM system.settings WHERE name = 'max_memory_usage'",
|
||||
|
@ -297,6 +297,8 @@ def test_https_non_ssl_auth():
|
||||
|
||||
|
||||
def test_create_user():
|
||||
instance.query("DROP USER IF EXISTS emma")
|
||||
|
||||
instance.query("CREATE USER emma IDENTIFIED WITH ssl_certificate CN 'client3'")
|
||||
assert (
|
||||
execute_query_https("SELECT currentUser()", user="emma", cert_name="client3")
|
||||
@ -330,14 +332,16 @@ def test_create_user():
|
||||
instance.query(
|
||||
"SELECT name, auth_type, auth_params FROM system.users WHERE name IN ['emma', 'lucy'] ORDER BY name"
|
||||
)
|
||||
== 'emma\tssl_certificate\t{"common_names":["client2"]}\n'
|
||||
'lucy\tssl_certificate\t{"common_names":["client2","client3"]}\n'
|
||||
== "emma\t['ssl_certificate']\t['{\"common_names\":[\"client2\"]}']\n"
|
||||
'lucy\t[\'ssl_certificate\']\t[\'{"common_names":["client2","client3"]}\']\n'
|
||||
)
|
||||
|
||||
instance.query("DROP USER emma")
|
||||
instance.query("DROP USER IF EXISTS emma")
|
||||
|
||||
|
||||
def test_x509_san_support():
|
||||
instance.query("DROP USER IF EXISTS jemma")
|
||||
|
||||
assert (
|
||||
execute_query_native(
|
||||
instance, "SELECT currentUser()", user="jerome", cert_name="client4"
|
||||
@ -352,7 +356,7 @@ def test_x509_san_support():
|
||||
instance.query(
|
||||
"SELECT name, auth_type, auth_params FROM system.users WHERE name='jerome'"
|
||||
)
|
||||
== 'jerome\tssl_certificate\t{"subject_alt_names":["URI:spiffe:\\\\/\\\\/foo.com\\\\/bar","URI:spiffe:\\\\/\\\\/foo.com\\\\/baz"]}\n'
|
||||
== 'jerome\t[\'ssl_certificate\']\t[\'{"subject_alt_names":["URI:spiffe:\\\\/\\\\/foo.com\\\\/bar","URI:spiffe:\\\\/\\\\/foo.com\\\\/baz"]}\']\n'
|
||||
)
|
||||
# user `jerome` is configured via xml config, but `show create` should work regardless.
|
||||
assert (
|
||||
@ -372,7 +376,7 @@ def test_x509_san_support():
|
||||
== "CREATE USER jemma IDENTIFIED WITH ssl_certificate SAN \\'URI:spiffe://foo.com/bar\\', \\'URI:spiffe://foo.com/baz\\'\n"
|
||||
)
|
||||
|
||||
instance.query("DROP USER jemma")
|
||||
instance.query("DROP USER IF EXISTS jemma")
|
||||
|
||||
|
||||
def test_x509_san_wildcard_support():
|
||||
@ -387,7 +391,7 @@ def test_x509_san_wildcard_support():
|
||||
instance.query(
|
||||
"SELECT name, auth_type, auth_params FROM system.users WHERE name='stewie'"
|
||||
)
|
||||
== 'stewie\tssl_certificate\t{"subject_alt_names":["URI:spiffe:\\\\/\\\\/bar.com\\\\/foo\\\\/*\\\\/far"]}\n'
|
||||
== "stewie\t['ssl_certificate']\t['{\"subject_alt_names\":[\"URI:spiffe:\\\\/\\\\/bar.com\\\\/foo\\\\/*\\\\/far\"]}']\n"
|
||||
)
|
||||
|
||||
assert (
|
||||
|
@ -186,6 +186,8 @@ def test_https_non_ssl_auth():
|
||||
|
||||
|
||||
def test_create_user():
|
||||
instance.query("DROP USER IF EXISTS emma")
|
||||
|
||||
instance.query("CREATE USER emma IDENTIFIED WITH ssl_certificate CN 'client3'")
|
||||
assert (
|
||||
execute_query_https("SELECT currentUser()", user="emma", cert_name="client3")
|
||||
@ -219,6 +221,8 @@ def test_create_user():
|
||||
instance.query(
|
||||
"SELECT name, auth_type, auth_params FROM system.users WHERE name IN ['emma', 'lucy'] ORDER BY name"
|
||||
)
|
||||
== 'emma\tssl_certificate\t{"common_names":["client2"]}\n'
|
||||
'lucy\tssl_certificate\t{"common_names":["client2","client3"]}\n'
|
||||
== "emma\t['ssl_certificate']\t['{\"common_names\":[\"client2\"]}']\n"
|
||||
'lucy\t[\'ssl_certificate\']\t[\'{"common_names":["client2","client3"]}\']\n'
|
||||
)
|
||||
|
||||
instance.query("DROP USER IF EXISTS emma")
|
||||
|
@ -19,10 +19,15 @@ def started_cluster():
|
||||
|
||||
|
||||
def test_basic(started_cluster):
|
||||
node.query("DROP USER IF EXISTS user_basic")
|
||||
|
||||
# 1. Without VALID UNTIL
|
||||
node.query("CREATE USER user_basic")
|
||||
|
||||
assert node.query("SHOW CREATE USER user_basic") == "CREATE USER user_basic\n"
|
||||
assert (
|
||||
node.query("SHOW CREATE USER user_basic")
|
||||
== "CREATE USER user_basic IDENTIFIED WITH no_password\n"
|
||||
)
|
||||
assert node.query("SELECT 1", user="user_basic") == "1\n"
|
||||
|
||||
# 2. With valid VALID UNTIL
|
||||
@ -30,7 +35,7 @@ def test_basic(started_cluster):
|
||||
|
||||
assert (
|
||||
node.query("SHOW CREATE USER user_basic")
|
||||
== "CREATE USER user_basic VALID UNTIL \\'2040-11-06 05:03:20\\'\n"
|
||||
== "CREATE USER user_basic IDENTIFIED WITH no_password VALID UNTIL \\'2040-11-06 05:03:20\\'\n"
|
||||
)
|
||||
assert node.query("SELECT 1", user="user_basic") == "1\n"
|
||||
|
||||
@ -39,7 +44,7 @@ def test_basic(started_cluster):
|
||||
|
||||
assert (
|
||||
node.query("SHOW CREATE USER user_basic")
|
||||
== "CREATE USER user_basic VALID UNTIL \\'2010-11-06 05:03:20\\'\n"
|
||||
== "CREATE USER user_basic IDENTIFIED WITH no_password VALID UNTIL \\'2010-11-06 05:03:20\\'\n"
|
||||
)
|
||||
|
||||
error = "Authentication failed"
|
||||
@ -48,7 +53,10 @@ def test_basic(started_cluster):
|
||||
# 4. Reset VALID UNTIL
|
||||
node.query("ALTER USER user_basic VALID UNTIL 'infinity'")
|
||||
|
||||
assert node.query("SHOW CREATE USER user_basic") == "CREATE USER user_basic\n"
|
||||
assert (
|
||||
node.query("SHOW CREATE USER user_basic")
|
||||
== "CREATE USER user_basic IDENTIFIED WITH no_password\n"
|
||||
)
|
||||
assert node.query("SELECT 1", user="user_basic") == "1\n"
|
||||
node.query("DROP USER user_basic")
|
||||
|
||||
@ -65,41 +73,53 @@ def test_basic(started_cluster):
|
||||
error = "Authentication failed"
|
||||
assert error in node.query_and_get_error("SELECT 1", user="user_basic")
|
||||
|
||||
node.query("DROP USER IF EXISTS user_basic")
|
||||
|
||||
|
||||
def test_details(started_cluster):
|
||||
node.query("DROP USER IF EXISTS user_details_infinity, user_details_time_only")
|
||||
|
||||
# 1. Does not do anything
|
||||
node.query("CREATE USER user_details_infinity VALID UNTIL 'infinity'")
|
||||
|
||||
assert (
|
||||
node.query("SHOW CREATE USER user_details_infinity")
|
||||
== "CREATE USER user_details_infinity\n"
|
||||
== "CREATE USER user_details_infinity IDENTIFIED WITH no_password\n"
|
||||
)
|
||||
|
||||
# 2. Time only is not supported
|
||||
node.query("CREATE USER user_details_time_only VALID UNTIL '22:03:40'")
|
||||
node.query(
|
||||
"CREATE USER user_details_time_only IDENTIFIED WITH no_password VALID UNTIL '22:03:40'"
|
||||
)
|
||||
|
||||
until_year = datetime.today().strftime("%Y")
|
||||
|
||||
assert (
|
||||
node.query("SHOW CREATE USER user_details_time_only")
|
||||
== f"CREATE USER user_details_time_only VALID UNTIL \\'{until_year}-01-01 22:03:40\\'\n"
|
||||
== f"CREATE USER user_details_time_only IDENTIFIED WITH no_password VALID UNTIL \\'{until_year}-01-01 22:03:40\\'\n"
|
||||
)
|
||||
|
||||
node.query("DROP USER IF EXISTS user_details_infinity, user_details_time_only")
|
||||
|
||||
|
||||
def test_restart(started_cluster):
|
||||
node.query("DROP USER IF EXISTS user_restart")
|
||||
|
||||
node.query("CREATE USER user_restart VALID UNTIL '06/11/2010 08:03:20 Z+3'")
|
||||
|
||||
assert (
|
||||
node.query("SHOW CREATE USER user_restart")
|
||||
== "CREATE USER user_restart VALID UNTIL \\'2010-11-06 05:03:20\\'\n"
|
||||
== "CREATE USER user_restart IDENTIFIED WITH no_password VALID UNTIL \\'2010-11-06 05:03:20\\'\n"
|
||||
)
|
||||
|
||||
node.restart_clickhouse()
|
||||
|
||||
assert (
|
||||
node.query("SHOW CREATE USER user_restart")
|
||||
== "CREATE USER user_restart VALID UNTIL \\'2010-11-06 05:03:20\\'\n"
|
||||
== "CREATE USER user_restart IDENTIFIED WITH no_password VALID UNTIL \\'2010-11-06 05:03:20\\'\n"
|
||||
)
|
||||
|
||||
error = "Authentication failed"
|
||||
assert error in node.query_and_get_error("SELECT 1", user="user_restart")
|
||||
|
||||
node.query("DROP USER IF EXISTS user_restart")
|
||||
|
@ -1,5 +1,5 @@
|
||||
A
|
||||
CREATE USER test_user_01073
|
||||
CREATE USER test_user_01073 IDENTIFIED WITH no_password
|
||||
B
|
||||
C
|
||||
GRANT INSERT, ALTER DELETE ON *.* TO test_user_01073
|
||||
|
@ -1,3 +1,5 @@
|
||||
-- Tags: no-parallel
|
||||
|
||||
DROP USER IF EXISTS test_user_01073;
|
||||
DROP ROLE IF EXISTS test_role_01073;
|
||||
|
||||
|
@ -1,17 +1,17 @@
|
||||
CREATE USER test_user_01075
|
||||
CREATE USER test_user_01075
|
||||
CREATE USER test_user_01075 HOST NONE
|
||||
CREATE USER test_user_01075 HOST LOCAL
|
||||
CREATE USER test_user_01075 HOST IP \'192.168.23.15\'
|
||||
CREATE USER test_user_01075 HOST IP \'2001:db8:11a3:9d7:1f34:8a2e:7a0:765d\'
|
||||
CREATE USER test_user_01075 HOST LOCAL, IP \'2001:db8:11a3:9d7:1f34:8a2e:7a0:765d\'
|
||||
CREATE USER test_user_01075 HOST LOCAL
|
||||
CREATE USER test_user_01075 HOST NONE
|
||||
CREATE USER test_user_01075 HOST LIKE \'@.somesite.com\'
|
||||
CREATE USER test_user_01075 HOST REGEXP \'.*\\\\.anothersite\\\\.com\'
|
||||
CREATE USER test_user_01075 HOST REGEXP \'.*\\\\.anothersite\\\\.com\', \'.*\\\\.anothersite\\\\.org\'
|
||||
CREATE USER test_user_01075 HOST REGEXP \'.*\\\\.anothersite2\\\\.com\', \'.*\\\\.anothersite2\\\\.org\'
|
||||
CREATE USER test_user_01075 HOST REGEXP \'.*\\\\.anothersite3\\\\.com\', \'.*\\\\.anothersite3\\\\.org\'
|
||||
CREATE USER `test_user_01075_x@localhost` HOST LOCAL
|
||||
CREATE USER test_user_01075_x HOST LOCAL
|
||||
CREATE USER `test_user_01075_x@192.168.23.15` HOST LOCAL
|
||||
CREATE USER test_user_01075 IDENTIFIED WITH no_password
|
||||
CREATE USER test_user_01075 IDENTIFIED WITH no_password
|
||||
CREATE USER test_user_01075 IDENTIFIED WITH no_password HOST NONE
|
||||
CREATE USER test_user_01075 IDENTIFIED WITH no_password HOST LOCAL
|
||||
CREATE USER test_user_01075 IDENTIFIED WITH no_password HOST IP \'192.168.23.15\'
|
||||
CREATE USER test_user_01075 IDENTIFIED WITH no_password HOST IP \'2001:db8:11a3:9d7:1f34:8a2e:7a0:765d\'
|
||||
CREATE USER test_user_01075 IDENTIFIED WITH no_password HOST LOCAL, IP \'2001:db8:11a3:9d7:1f34:8a2e:7a0:765d\'
|
||||
CREATE USER test_user_01075 IDENTIFIED WITH no_password HOST LOCAL
|
||||
CREATE USER test_user_01075 IDENTIFIED WITH no_password HOST NONE
|
||||
CREATE USER test_user_01075 IDENTIFIED WITH no_password HOST LIKE \'@.somesite.com\'
|
||||
CREATE USER test_user_01075 IDENTIFIED WITH no_password HOST REGEXP \'.*\\\\.anothersite\\\\.com\'
|
||||
CREATE USER test_user_01075 IDENTIFIED WITH no_password HOST REGEXP \'.*\\\\.anothersite\\\\.com\', \'.*\\\\.anothersite\\\\.org\'
|
||||
CREATE USER test_user_01075 IDENTIFIED WITH no_password HOST REGEXP \'.*\\\\.anothersite2\\\\.com\', \'.*\\\\.anothersite2\\\\.org\'
|
||||
CREATE USER test_user_01075 IDENTIFIED WITH no_password HOST REGEXP \'.*\\\\.anothersite3\\\\.com\', \'.*\\\\.anothersite3\\\\.org\'
|
||||
CREATE USER `test_user_01075_x@localhost` IDENTIFIED WITH no_password HOST LOCAL
|
||||
CREATE USER test_user_01075_x IDENTIFIED WITH no_password HOST LOCAL
|
||||
CREATE USER `test_user_01075_x@192.168.23.15` IDENTIFIED WITH no_password HOST LOCAL
|
||||
|
@ -1,4 +1,4 @@
|
||||
-- Tags: no-fasttest
|
||||
-- Tags: no-fasttest, no-parallel
|
||||
|
||||
DROP USER IF EXISTS test_user_01075, test_user_01075_x, test_user_01075_x@localhost, test_user_01075_x@'192.168.23.15';
|
||||
|
||||
|
@ -1,12 +1,12 @@
|
||||
-- default
|
||||
CREATE USER u1_01292
|
||||
CREATE USER u1_01292 IDENTIFIED WITH no_password
|
||||
-- same as default
|
||||
CREATE USER u2_01292
|
||||
CREATE USER u3_01292
|
||||
CREATE USER u2_01292 IDENTIFIED WITH no_password
|
||||
CREATE USER u3_01292 IDENTIFIED WITH no_password
|
||||
-- rename
|
||||
CREATE USER u2_01292_renamed
|
||||
CREATE USER u2_01292_renamed IDENTIFIED WITH no_password
|
||||
-- authentication
|
||||
CREATE USER u1_01292
|
||||
CREATE USER u1_01292 IDENTIFIED WITH no_password
|
||||
CREATE USER u2_01292 IDENTIFIED WITH plaintext_password
|
||||
CREATE USER u3_01292 IDENTIFIED WITH sha256_password
|
||||
CREATE USER u4_01292 IDENTIFIED WITH sha256_password
|
||||
@ -19,97 +19,97 @@ CREATE USER u1_01292 IDENTIFIED WITH sha256_password
|
||||
CREATE USER u2_01292 IDENTIFIED WITH sha256_password
|
||||
CREATE USER u3_01292 IDENTIFIED WITH sha256_password
|
||||
CREATE USER u4_01292 IDENTIFIED WITH plaintext_password
|
||||
CREATE USER u5_01292
|
||||
CREATE USER u5_01292 IDENTIFIED WITH no_password
|
||||
-- host
|
||||
CREATE USER u1_01292
|
||||
CREATE USER u2_01292 HOST NONE
|
||||
CREATE USER u3_01292 HOST LOCAL
|
||||
CREATE USER u4_01292 HOST NAME \'myhost.com\'
|
||||
CREATE USER u5_01292 HOST LOCAL, NAME \'myhost.com\'
|
||||
CREATE USER u6_01292 HOST LOCAL, NAME \'myhost.com\'
|
||||
CREATE USER u7_01292 HOST REGEXP \'.*\\\\.myhost\\\\.com\'
|
||||
CREATE USER u8_01292
|
||||
CREATE USER u9_01292 HOST LIKE \'%.myhost.com\'
|
||||
CREATE USER u10_01292 HOST LIKE \'%.myhost.com\'
|
||||
CREATE USER u11_01292 HOST LOCAL
|
||||
CREATE USER u12_01292 HOST IP \'192.168.1.1\'
|
||||
CREATE USER u13_01292 HOST IP \'192.168.0.0/16\'
|
||||
CREATE USER u14_01292 HOST LOCAL
|
||||
CREATE USER u15_01292 HOST IP \'2001:db8:11a3:9d7:1f34:8a2e:7a0:765d\'
|
||||
CREATE USER u16_01292 HOST LOCAL, IP \'65:ff0c::/96\'
|
||||
CREATE USER u1_01292 HOST NONE
|
||||
CREATE USER u2_01292 HOST NAME \'myhost.com\'
|
||||
CREATE USER u3_01292 HOST LOCAL, NAME \'myhost.com\'
|
||||
CREATE USER u4_01292 HOST NONE
|
||||
CREATE USER u1_01292 IDENTIFIED WITH no_password
|
||||
CREATE USER u2_01292 IDENTIFIED WITH no_password HOST NONE
|
||||
CREATE USER u3_01292 IDENTIFIED WITH no_password HOST LOCAL
|
||||
CREATE USER u4_01292 IDENTIFIED WITH no_password HOST NAME \'myhost.com\'
|
||||
CREATE USER u5_01292 IDENTIFIED WITH no_password HOST LOCAL, NAME \'myhost.com\'
|
||||
CREATE USER u6_01292 IDENTIFIED WITH no_password HOST LOCAL, NAME \'myhost.com\'
|
||||
CREATE USER u7_01292 IDENTIFIED WITH no_password HOST REGEXP \'.*\\\\.myhost\\\\.com\'
|
||||
CREATE USER u8_01292 IDENTIFIED WITH no_password
|
||||
CREATE USER u9_01292 IDENTIFIED WITH no_password HOST LIKE \'%.myhost.com\'
|
||||
CREATE USER u10_01292 IDENTIFIED WITH no_password HOST LIKE \'%.myhost.com\'
|
||||
CREATE USER u11_01292 IDENTIFIED WITH no_password HOST LOCAL
|
||||
CREATE USER u12_01292 IDENTIFIED WITH no_password HOST IP \'192.168.1.1\'
|
||||
CREATE USER u13_01292 IDENTIFIED WITH no_password HOST IP \'192.168.0.0/16\'
|
||||
CREATE USER u14_01292 IDENTIFIED WITH no_password HOST LOCAL
|
||||
CREATE USER u15_01292 IDENTIFIED WITH no_password HOST IP \'2001:db8:11a3:9d7:1f34:8a2e:7a0:765d\'
|
||||
CREATE USER u16_01292 IDENTIFIED WITH no_password HOST LOCAL, IP \'65:ff0c::/96\'
|
||||
CREATE USER u1_01292 IDENTIFIED WITH no_password HOST NONE
|
||||
CREATE USER u2_01292 IDENTIFIED WITH no_password HOST NAME \'myhost.com\'
|
||||
CREATE USER u3_01292 IDENTIFIED WITH no_password HOST LOCAL, NAME \'myhost.com\'
|
||||
CREATE USER u4_01292 IDENTIFIED WITH no_password HOST NONE
|
||||
-- host after @
|
||||
CREATE USER u1_01292
|
||||
CREATE USER u1_01292
|
||||
CREATE USER `u2_01292@%.myhost.com` HOST LIKE \'%.myhost.com\'
|
||||
CREATE USER `u2_01292@%.myhost.com` HOST LIKE \'%.myhost.com\'
|
||||
CREATE USER `u3_01292@192.168.%.%` HOST LIKE \'192.168.%.%\'
|
||||
CREATE USER `u3_01292@192.168.%.%` HOST LIKE \'192.168.%.%\'
|
||||
CREATE USER `u4_01292@::1` HOST LOCAL
|
||||
CREATE USER `u4_01292@::1` HOST LOCAL
|
||||
CREATE USER `u5_01292@65:ff0c::/96` HOST LIKE \'65:ff0c::/96\'
|
||||
CREATE USER `u5_01292@65:ff0c::/96` HOST LIKE \'65:ff0c::/96\'
|
||||
CREATE USER u1_01292 HOST LOCAL
|
||||
CREATE USER `u2_01292@%.myhost.com`
|
||||
CREATE USER u1_01292 IDENTIFIED WITH no_password
|
||||
CREATE USER u1_01292 IDENTIFIED WITH no_password
|
||||
CREATE USER `u2_01292@%.myhost.com` IDENTIFIED WITH no_password HOST LIKE \'%.myhost.com\'
|
||||
CREATE USER `u2_01292@%.myhost.com` IDENTIFIED WITH no_password HOST LIKE \'%.myhost.com\'
|
||||
CREATE USER `u3_01292@192.168.%.%` IDENTIFIED WITH no_password HOST LIKE \'192.168.%.%\'
|
||||
CREATE USER `u3_01292@192.168.%.%` IDENTIFIED WITH no_password HOST LIKE \'192.168.%.%\'
|
||||
CREATE USER `u4_01292@::1` IDENTIFIED WITH no_password HOST LOCAL
|
||||
CREATE USER `u4_01292@::1` IDENTIFIED WITH no_password HOST LOCAL
|
||||
CREATE USER `u5_01292@65:ff0c::/96` IDENTIFIED WITH no_password HOST LIKE \'65:ff0c::/96\'
|
||||
CREATE USER `u5_01292@65:ff0c::/96` IDENTIFIED WITH no_password HOST LIKE \'65:ff0c::/96\'
|
||||
CREATE USER u1_01292 IDENTIFIED WITH no_password HOST LOCAL
|
||||
CREATE USER `u2_01292@%.myhost.com` IDENTIFIED WITH no_password
|
||||
-- settings
|
||||
CREATE USER u1_01292
|
||||
CREATE USER u2_01292 SETTINGS PROFILE `default`
|
||||
CREATE USER u3_01292 SETTINGS max_memory_usage = 5000000
|
||||
CREATE USER u4_01292 SETTINGS max_memory_usage MIN 5000000
|
||||
CREATE USER u5_01292 SETTINGS max_memory_usage MAX 5000000
|
||||
CREATE USER u6_01292 SETTINGS max_memory_usage CONST
|
||||
CREATE USER u7_01292 SETTINGS max_memory_usage WRITABLE
|
||||
CREATE USER u8_01292 SETTINGS max_memory_usage = 5000000 MIN 4000000 MAX 6000000 CONST
|
||||
CREATE USER u9_01292 SETTINGS PROFILE `default`, max_memory_usage = 5000000 WRITABLE
|
||||
CREATE USER u1_01292 SETTINGS readonly = 1
|
||||
CREATE USER u2_01292 SETTINGS readonly = 1
|
||||
CREATE USER u3_01292
|
||||
CREATE USER u1_01292 IDENTIFIED WITH no_password
|
||||
CREATE USER u2_01292 IDENTIFIED WITH no_password SETTINGS PROFILE `default`
|
||||
CREATE USER u3_01292 IDENTIFIED WITH no_password SETTINGS max_memory_usage = 5000000
|
||||
CREATE USER u4_01292 IDENTIFIED WITH no_password SETTINGS max_memory_usage MIN 5000000
|
||||
CREATE USER u5_01292 IDENTIFIED WITH no_password SETTINGS max_memory_usage MAX 5000000
|
||||
CREATE USER u6_01292 IDENTIFIED WITH no_password SETTINGS max_memory_usage CONST
|
||||
CREATE USER u7_01292 IDENTIFIED WITH no_password SETTINGS max_memory_usage WRITABLE
|
||||
CREATE USER u8_01292 IDENTIFIED WITH no_password SETTINGS max_memory_usage = 5000000 MIN 4000000 MAX 6000000 CONST
|
||||
CREATE USER u9_01292 IDENTIFIED WITH no_password SETTINGS PROFILE `default`, max_memory_usage = 5000000 WRITABLE
|
||||
CREATE USER u1_01292 IDENTIFIED WITH no_password SETTINGS readonly = 1
|
||||
CREATE USER u2_01292 IDENTIFIED WITH no_password SETTINGS readonly = 1
|
||||
CREATE USER u3_01292 IDENTIFIED WITH no_password
|
||||
-- default role
|
||||
CREATE USER u1_01292
|
||||
CREATE USER u2_01292 DEFAULT ROLE NONE
|
||||
CREATE USER u3_01292 DEFAULT ROLE r1_01292
|
||||
CREATE USER u4_01292 DEFAULT ROLE r1_01292, r2_01292
|
||||
CREATE USER u5_01292 DEFAULT ROLE ALL EXCEPT r2_01292
|
||||
CREATE USER u6_01292 DEFAULT ROLE ALL EXCEPT r1_01292, r2_01292
|
||||
CREATE USER u1_01292 DEFAULT ROLE r1_01292
|
||||
CREATE USER u2_01292 DEFAULT ROLE ALL EXCEPT r2_01292
|
||||
CREATE USER u3_01292 DEFAULT ROLE r2_01292
|
||||
CREATE USER u4_01292
|
||||
CREATE USER u5_01292 DEFAULT ROLE ALL EXCEPT r1_01292
|
||||
CREATE USER u6_01292 DEFAULT ROLE NONE
|
||||
CREATE USER u1_01292 IDENTIFIED WITH no_password
|
||||
CREATE USER u2_01292 IDENTIFIED WITH no_password DEFAULT ROLE NONE
|
||||
CREATE USER u3_01292 IDENTIFIED WITH no_password DEFAULT ROLE r1_01292
|
||||
CREATE USER u4_01292 IDENTIFIED WITH no_password DEFAULT ROLE r1_01292, r2_01292
|
||||
CREATE USER u5_01292 IDENTIFIED WITH no_password DEFAULT ROLE ALL EXCEPT r2_01292
|
||||
CREATE USER u6_01292 IDENTIFIED WITH no_password DEFAULT ROLE ALL EXCEPT r1_01292, r2_01292
|
||||
CREATE USER u1_01292 IDENTIFIED WITH no_password DEFAULT ROLE r1_01292
|
||||
CREATE USER u2_01292 IDENTIFIED WITH no_password DEFAULT ROLE ALL EXCEPT r2_01292
|
||||
CREATE USER u3_01292 IDENTIFIED WITH no_password DEFAULT ROLE r2_01292
|
||||
CREATE USER u4_01292 IDENTIFIED WITH no_password
|
||||
CREATE USER u5_01292 IDENTIFIED WITH no_password DEFAULT ROLE ALL EXCEPT r1_01292
|
||||
CREATE USER u6_01292 IDENTIFIED WITH no_password DEFAULT ROLE NONE
|
||||
-- complex
|
||||
CREATE USER u1_01292 IDENTIFIED WITH plaintext_password HOST LOCAL SETTINGS readonly = 1
|
||||
CREATE USER u1_01292 HOST LIKE \'%.%.myhost.com\' DEFAULT ROLE NONE SETTINGS PROFILE `default`
|
||||
CREATE USER u1_01292 IDENTIFIED WITH no_password HOST LIKE \'%.%.myhost.com\' DEFAULT ROLE NONE SETTINGS PROFILE `default`
|
||||
-- if not exists
|
||||
CREATE USER u1_01292
|
||||
CREATE USER u1_01292 IDENTIFIED WITH no_password
|
||||
GRANT r1_01292 TO u1_01292
|
||||
-- if not exists-part2
|
||||
CREATE USER u1_01292
|
||||
CREATE USER u1_01292 IDENTIFIED WITH no_password
|
||||
GRANT r1_01292, r2_01292 TO u1_01292
|
||||
-- or replace
|
||||
CREATE USER u1_01292
|
||||
CREATE USER u2_01292
|
||||
CREATE USER u1_01292 IDENTIFIED WITH no_password
|
||||
CREATE USER u2_01292 IDENTIFIED WITH no_password
|
||||
-- multiple users in one command
|
||||
CREATE USER u1_01292 DEFAULT ROLE NONE
|
||||
CREATE USER u2_01292 DEFAULT ROLE NONE
|
||||
CREATE USER u3_01292 HOST LIKE \'%.%.myhost.com\'
|
||||
CREATE USER u4_01292 HOST LIKE \'%.%.myhost.com\'
|
||||
CREATE USER `u5_01292@%.host.com` HOST LIKE \'%.host.com\'
|
||||
CREATE USER `u6_01292@%.host.com` HOST LIKE \'%.host.com\'
|
||||
CREATE USER `u7_01292@%.host.com` HOST LIKE \'%.host.com\'
|
||||
CREATE USER `u8_01292@%.otherhost.com` HOST LIKE \'%.otherhost.com\'
|
||||
CREATE USER u1_01292 DEFAULT ROLE NONE SETTINGS readonly = 1
|
||||
CREATE USER u2_01292 DEFAULT ROLE r1_01292, r2_01292 SETTINGS readonly = 1
|
||||
CREATE USER u3_01292 HOST LIKE \'%.%.myhost.com\' DEFAULT ROLE r1_01292, r2_01292
|
||||
CREATE USER u4_01292 HOST LIKE \'%.%.myhost.com\' DEFAULT ROLE r1_01292, r2_01292
|
||||
CREATE USER u1_01292 IDENTIFIED WITH no_password DEFAULT ROLE NONE
|
||||
CREATE USER u2_01292 IDENTIFIED WITH no_password DEFAULT ROLE NONE
|
||||
CREATE USER u3_01292 IDENTIFIED WITH no_password HOST LIKE \'%.%.myhost.com\'
|
||||
CREATE USER u4_01292 IDENTIFIED WITH no_password HOST LIKE \'%.%.myhost.com\'
|
||||
CREATE USER `u5_01292@%.host.com` IDENTIFIED WITH no_password HOST LIKE \'%.host.com\'
|
||||
CREATE USER `u6_01292@%.host.com` IDENTIFIED WITH no_password HOST LIKE \'%.host.com\'
|
||||
CREATE USER `u7_01292@%.host.com` IDENTIFIED WITH no_password HOST LIKE \'%.host.com\'
|
||||
CREATE USER `u8_01292@%.otherhost.com` IDENTIFIED WITH no_password HOST LIKE \'%.otherhost.com\'
|
||||
CREATE USER u1_01292 IDENTIFIED WITH no_password DEFAULT ROLE NONE SETTINGS readonly = 1
|
||||
CREATE USER u2_01292 IDENTIFIED WITH no_password DEFAULT ROLE r1_01292, r2_01292 SETTINGS readonly = 1
|
||||
CREATE USER u3_01292 IDENTIFIED WITH no_password HOST LIKE \'%.%.myhost.com\' DEFAULT ROLE r1_01292, r2_01292
|
||||
CREATE USER u4_01292 IDENTIFIED WITH no_password HOST LIKE \'%.%.myhost.com\' DEFAULT ROLE r1_01292, r2_01292
|
||||
-- system.users
|
||||
u1_01292 local_directory plaintext_password {} [] ['localhost'] [] [] 1 [] []
|
||||
u2_01292 local_directory no_password {} [] [] [] ['%.%.myhost.com'] 0 [] []
|
||||
u3_01292 local_directory sha256_password {} ['192.169.1.1','192.168.0.0/16'] ['localhost'] [] [] 0 ['r1_01292'] []
|
||||
u4_01292 local_directory double_sha1_password {} ['::/0'] [] [] [] 1 [] ['r1_01292']
|
||||
u1_01292 local_directory ['plaintext_password'] ['{}'] [] ['localhost'] [] [] 1 [] []
|
||||
u2_01292 local_directory ['no_password'] ['{}'] [] [] [] ['%.%.myhost.com'] 0 [] []
|
||||
u3_01292 local_directory ['sha256_password'] ['{}'] ['192.169.1.1','192.168.0.0/16'] ['localhost'] [] [] 0 ['r1_01292'] []
|
||||
u4_01292 local_directory ['double_sha1_password'] ['{}'] ['::/0'] [] [] [] 1 [] ['r1_01292']
|
||||
-- system.settings_profile_elements
|
||||
\N u1_01292 \N 0 readonly 1 \N \N \N \N
|
||||
\N u2_01292 \N 0 \N \N \N \N \N default
|
||||
@ -117,3 +117,5 @@ u4_01292 local_directory double_sha1_password {} ['::/0'] [] [] [] 1 [] ['r1_012
|
||||
\N u4_01292 \N 0 \N \N \N \N \N default
|
||||
\N u4_01292 \N 1 max_memory_usage 5000000 \N \N \N \N
|
||||
\N u4_01292 \N 2 readonly 1 \N \N \N \N
|
||||
-- multiple authentication methods
|
||||
u1_01292 ['plaintext_password','kerberos','bcrypt_password','ldap'] ['{}','{"realm":"qwerty10"}','{}','{"server":"abc"}']
|
||||
|
@ -233,3 +233,8 @@ SELECT * FROM system.settings_profile_elements WHERE user_name LIKE 'u%\_01292'
|
||||
DROP USER u1_01292, u2_01292, u3_01292, u4_01292, u5_01292;
|
||||
|
||||
DROP ROLE r1_01292, r2_01292;
|
||||
|
||||
SELECT '-- multiple authentication methods';
|
||||
CREATE USER u1_01292 IDENTIFIED WITH plaintext_password by '1', kerberos REALM 'qwerty10', bcrypt_password by '3', ldap SERVER 'abc';
|
||||
SELECT name, auth_type, auth_params FROM system.users WHERE name = 'u1_01292' ORDER BY name;
|
||||
DROP USER u1_01292;
|
||||
|
@ -1 +1 @@
|
||||
[1mCREATE USER[0m user[1m IDENTIFIED[0m[1m WITH plaintext_password[0m[1m BY[0m 'hello'
|
||||
[1mCREATE USER[0m user[1m IDENTIFIED[0m[1m WITH[0m[1m plaintext_password[0m[1m BY[0m 'hello'
|
||||
|
@ -1,4 +1,4 @@
|
||||
default
|
||||
db_01939
|
||||
CREATE USER u_01939
|
||||
CREATE USER u_01939 DEFAULT DATABASE NONE
|
||||
CREATE USER u_01939 IDENTIFIED WITH no_password
|
||||
CREATE USER u_01939 IDENTIFIED WITH no_password DEFAULT DATABASE NONE
|
||||
|
@ -1,4 +1,4 @@
|
||||
CREATE USER test_user_01999
|
||||
CREATE USER test_user_01999 IDENTIFIED WITH no_password
|
||||
A
|
||||
B
|
||||
GRANT SELECT ON db1.* TO test_user_01999
|
||||
|
@ -1143,8 +1143,8 @@ CREATE TABLE system.users
|
||||
`name` String,
|
||||
`id` UUID,
|
||||
`storage` String,
|
||||
`auth_type` Enum8('no_password' = 0, 'plaintext_password' = 1, 'sha256_password' = 2, 'double_sha1_password' = 3, 'ldap' = 4, 'kerberos' = 5, 'ssl_certificate' = 6, 'bcrypt_password' = 7, 'ssh_key' = 8, 'http' = 9, 'jwt' = 10),
|
||||
`auth_params` String,
|
||||
`auth_type` Array(Enum8('no_password' = 0, 'plaintext_password' = 1, 'sha256_password' = 2, 'double_sha1_password' = 3, 'ldap' = 4, 'kerberos' = 5, 'ssl_certificate' = 6, 'bcrypt_password' = 7, 'ssh_key' = 8, 'http' = 9, 'jwt' = 10)),
|
||||
`auth_params` Array(String),
|
||||
`host_ip` Array(String),
|
||||
`host_names` Array(String),
|
||||
`host_names_regexp` Array(String),
|
||||
|
@ -0,0 +1,66 @@
|
||||
localhost 9000 0 0 0
|
||||
localhost 9000 0 0 0
|
||||
Basic authentication after user creation
|
||||
1
|
||||
localhost 9000 0 0 0
|
||||
Changed password, old password should not work
|
||||
AUTHENTICATION_FAILED
|
||||
New password should work
|
||||
1
|
||||
localhost 9000 0 0 0
|
||||
Two new passwords were added, should both work
|
||||
1
|
||||
1
|
||||
localhost 9000 0 0 0
|
||||
Authenticating with ssh key
|
||||
1
|
||||
Altering credentials and keeping only bcrypt_password
|
||||
localhost 9000 0 0 0
|
||||
Asserting SSH does not work anymore
|
||||
AUTHENTICATION_FAILED
|
||||
Asserting bcrypt_password works
|
||||
1
|
||||
Adding new bcrypt_password
|
||||
localhost 9000 0 0 0
|
||||
Both current authentication methods should work
|
||||
1
|
||||
1
|
||||
Reset authentication methods to new
|
||||
localhost 9000 0 0 0
|
||||
Only the latest should work, below should fail
|
||||
AUTHENTICATION_FAILED
|
||||
Should work
|
||||
1
|
||||
Multiple identified with, not allowed
|
||||
Syntax error
|
||||
localhost 9000 0 0 0
|
||||
CREATE Multiple identified with, not allowed
|
||||
Syntax error
|
||||
localhost 9000 0 0 0
|
||||
Create user with no identification
|
||||
localhost 9000 0 0 0
|
||||
Add identified with, should not be allowed because user is currently identified with no_password and it can not co-exist with other auth types
|
||||
BAD_ARGUMENTS
|
||||
Try to add no_password mixed with other authentication methods, should not be allowed
|
||||
SYNTAX_ERROR
|
||||
Adding no_password, should fail
|
||||
SYNTAX_ERROR
|
||||
Replacing existing authentication methods in favor of no_password, should succeed
|
||||
localhost 9000 0 0 0
|
||||
Trying to auth with no pwd, should succeed
|
||||
1
|
||||
localhost 9000 0 0 0
|
||||
Use WITH without providing authentication type, should fail
|
||||
Syntax error
|
||||
Create user with ADD identification, should fail, add is not allowed for create query
|
||||
SYNTAX_ERROR
|
||||
Trailing comma should result in syntax error
|
||||
SYNTAX_ERROR
|
||||
First auth method can't specify type if WITH keyword is not present
|
||||
SYNTAX_ERROR
|
||||
RESET AUTHENTICATION METHODS TO NEW can only be used on alter statement
|
||||
SYNTAX_ERROR
|
||||
ADD NOT IDENTIFIED should result in syntax error
|
||||
SYNTAX_ERROR
|
||||
RESET AUTHENTICATION METHODS TO NEW cannot be used along with [ADD] IDENTIFIED clauses
|
||||
SYNTAX_ERROR
|
147
tests/queries/0_stateless/03174_multiple_authentication_methods.sh
Executable file
147
tests/queries/0_stateless/03174_multiple_authentication_methods.sh
Executable file
@ -0,0 +1,147 @@
|
||||
#!/usr/bin/env bash
|
||||
# Tags: no-fasttest, no-replicated-database
|
||||
# Tag no-replicated-database: https://s3.amazonaws.com/clickhouse-test-reports/65277/43e9a7ba4bbf7f20145531b384a31304895b55bc/stateless_tests__release__old_analyzer__s3__databasereplicated__[1_2].html and https://github.com/ClickHouse/ClickHouse/blob/011c694117845500c82f9563c65930429979982f/tests/queries/0_stateless/01175_distributed_ddl_output_mode_long.sh#L4
|
||||
|
||||
CURDIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
|
||||
# shellcheck source=../shell_config.sh
|
||||
. "$CURDIR"/../shell_config.sh
|
||||
|
||||
ssh_key="-----BEGIN OPENSSH PRIVATE KEY-----
|
||||
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
|
||||
QyNTUxOQAAACAc6mt3bktHHukGJM1IJKPVtFMe4u3d8T6LHha8J4WOGAAAAJApc2djKXNn
|
||||
YwAAAAtzc2gtZWQyNTUxOQAAACAc6mt3bktHHukGJM1IJKPVtFMe4u3d8T6LHha8J4WOGA
|
||||
AAAEAk15S5L7j85LvmAivo2J8lo44OR/tLILBO1Wb2//mFwBzqa3duS0ce6QYkzUgko9W0
|
||||
Ux7i7d3xPoseFrwnhY4YAAAADWFydGh1ckBhcnRodXI=
|
||||
-----END OPENSSH PRIVATE KEY-----"
|
||||
|
||||
function test_login_no_pwd
|
||||
{
|
||||
${CLICKHOUSE_CURL} -sS "${CLICKHOUSE_URL}&user=$1" -d "select 1"
|
||||
}
|
||||
|
||||
function test_login_pwd
|
||||
{
|
||||
${CLICKHOUSE_CURL} -sS "${CLICKHOUSE_URL}&user=$1&password=$2" -d "select 1"
|
||||
}
|
||||
|
||||
function test_login_pwd_expect_error
|
||||
{
|
||||
test_login_pwd "$1" "$2" 2>&1 | grep -m1 -o 'AUTHENTICATION_FAILED' | head -n 1
|
||||
}
|
||||
|
||||
function test
|
||||
{
|
||||
user="u01_03174$RANDOM"
|
||||
|
||||
${CLICKHOUSE_CURL} -sS "${CLICKHOUSE_URL}" -d "DROP USER IF EXISTS ${user} $1"
|
||||
|
||||
${CLICKHOUSE_CURL} -sS "${CLICKHOUSE_URL}" -d "CREATE USER ${user} $1 IDENTIFIED WITH plaintext_password BY '1'"
|
||||
|
||||
echo "Basic authentication after user creation"
|
||||
test_login_pwd ${user} '1'
|
||||
|
||||
${CLICKHOUSE_CURL} -sS "${CLICKHOUSE_URL}" -d "ALTER USER ${user} $1 IDENTIFIED WITH plaintext_password BY '2'"
|
||||
|
||||
echo "Changed password, old password should not work"
|
||||
test_login_pwd_expect_error ${user} '1'
|
||||
|
||||
echo "New password should work"
|
||||
test_login_pwd ${user} '2'
|
||||
|
||||
${CLICKHOUSE_CURL} -sS "${CLICKHOUSE_URL}" -d "ALTER USER ${user} $1 ADD IDENTIFIED WITH plaintext_password BY '3', plaintext_password BY '4'"
|
||||
|
||||
echo "Two new passwords were added, should both work"
|
||||
test_login_pwd ${user} '3'
|
||||
|
||||
test_login_pwd ${user} '4'
|
||||
|
||||
ssh_pub_key="AAAAC3NzaC1lZDI1NTE5AAAAIBzqa3duS0ce6QYkzUgko9W0Ux7i7d3xPoseFrwnhY4Y"
|
||||
|
||||
${CLICKHOUSE_CURL} -sS "${CLICKHOUSE_URL}" -d "ALTER USER ${user} $1 ADD IDENTIFIED WITH ssh_key BY KEY '${ssh_pub_key}' TYPE 'ssh-ed25519'"
|
||||
|
||||
echo ${ssh_key} > ssh_key
|
||||
|
||||
echo "Authenticating with ssh key"
|
||||
${CLICKHOUSE_CLIENT} --user ${user} --ssh-key-file 'ssh_key' --ssh-key-passphrase "" --query "SELECT 1"
|
||||
|
||||
echo "Altering credentials and keeping only bcrypt_password"
|
||||
${CLICKHOUSE_CURL} -sS "${CLICKHOUSE_URL}" -d "ALTER USER ${user} $1 IDENTIFIED WITH bcrypt_password BY '5'"
|
||||
|
||||
echo "Asserting SSH does not work anymore"
|
||||
${CLICKHOUSE_CLIENT} --user ${user} --ssh-key-file 'ssh_key' --ssh-key-passphrase "" --query "SELECT 1" 2>&1 | grep -m1 -o 'AUTHENTICATION_FAILED' | head -n 1
|
||||
|
||||
echo "Asserting bcrypt_password works"
|
||||
test_login_pwd ${user} '5'
|
||||
|
||||
echo "Adding new bcrypt_password"
|
||||
${CLICKHOUSE_CURL} -sS "${CLICKHOUSE_URL}" -d "ALTER USER ${user} $1 ADD IDENTIFIED WITH bcrypt_password BY '6'"
|
||||
|
||||
echo "Both current authentication methods should work"
|
||||
test_login_pwd ${user} '5'
|
||||
test_login_pwd ${user} '6'
|
||||
|
||||
echo "Reset authentication methods to new"
|
||||
${CLICKHOUSE_CURL} -sS "${CLICKHOUSE_URL}" -d "ALTER USER ${user} $1 RESET AUTHENTICATION METHODS TO NEW"
|
||||
|
||||
echo "Only the latest should work, below should fail"
|
||||
test_login_pwd_expect_error ${user} '5'
|
||||
|
||||
echo "Should work"
|
||||
test_login_pwd ${user} '6'
|
||||
|
||||
echo "Multiple identified with, not allowed"
|
||||
${CLICKHOUSE_CURL} -sS "${CLICKHOUSE_URL}" -d "ALTER USER ${user} $1 IDENTIFIED WITH plaintext_password by '7', IDENTIFIED plaintext_password by '8'" 2>&1 | grep -m1 -o "Syntax error" | head -n 1
|
||||
|
||||
${CLICKHOUSE_CURL} -sS "${CLICKHOUSE_URL}" -d "DROP USER ${user} $1"
|
||||
|
||||
echo "CREATE Multiple identified with, not allowed"
|
||||
${CLICKHOUSE_CURL} -sS "${CLICKHOUSE_URL}" -d "CREATE USER ${user} $1 IDENTIFIED WITH plaintext_password by '7', IDENTIFIED WITH plaintext_password by '8'" 2>&1 | grep -m1 -o "Syntax error" | head -n 1
|
||||
|
||||
${CLICKHOUSE_CURL} -sS "${CLICKHOUSE_URL}" -d "DROP USER IF EXISTS ${user} $1"
|
||||
|
||||
echo "Create user with no identification"
|
||||
${CLICKHOUSE_CURL} -sS "${CLICKHOUSE_URL}" -d "CREATE USER ${user} $1"
|
||||
|
||||
echo "Add identified with, should not be allowed because user is currently identified with no_password and it can not co-exist with other auth types"
|
||||
${CLICKHOUSE_CURL} -sS "${CLICKHOUSE_URL}" -d "ALTER USER ${user} $1 ADD IDENTIFIED WITH plaintext_password by '7'" 2>&1 | grep -m1 -o "BAD_ARGUMENTS" | head -n 1
|
||||
|
||||
echo "Try to add no_password mixed with other authentication methods, should not be allowed"
|
||||
${CLICKHOUSE_CURL} -sS "${CLICKHOUSE_URL}" -d "ALTER USER ${user} $1 ADD IDENTIFIED WITH plaintext_password by '8', no_password" 2>&1 | grep -m1 -o "SYNTAX_ERROR" | head -n 1
|
||||
|
||||
echo "Adding no_password, should fail"
|
||||
${CLICKHOUSE_CURL} -sS "${CLICKHOUSE_URL}" -d "ALTER USER ${user} $1 ADD IDENTIFIED WITH no_password" 2>&1 | grep -m1 -o "SYNTAX_ERROR" | head -n 1
|
||||
|
||||
echo "Replacing existing authentication methods in favor of no_password, should succeed"
|
||||
${CLICKHOUSE_CURL} -sS "${CLICKHOUSE_URL}" -d "ALTER USER ${user} $1 IDENTIFIED WITH no_password"
|
||||
|
||||
echo "Trying to auth with no pwd, should succeed"
|
||||
test_login_no_pwd ${user}
|
||||
|
||||
${CLICKHOUSE_CURL} -sS "${CLICKHOUSE_URL}" -d "DROP USER IF EXISTS ${user} $1"
|
||||
|
||||
echo "Use WITH without providing authentication type, should fail"
|
||||
${CLICKHOUSE_CURL} -sS "${CLICKHOUSE_URL}" -d "CREATE USER ${user} $1 IDENTIFIED WITH BY '1';" 2>&1 | grep -m1 -o "Syntax error" | head -n 1
|
||||
|
||||
echo "Create user with ADD identification, should fail, add is not allowed for create query"
|
||||
${CLICKHOUSE_CURL} -sS "${CLICKHOUSE_URL}" -d "CREATE USER ${user} $1 ADD IDENTIFIED WITH plaintext_password by '1'" 2>&1 | grep -m1 -o "SYNTAX_ERROR" | head -n 1
|
||||
|
||||
echo "Trailing comma should result in syntax error"
|
||||
${CLICKHOUSE_CURL} -sS "${CLICKHOUSE_URL}" -d "ALTER USER ${user} $1 ADD IDENTIFIED WITH plaintext_password by '1'," 2>&1 | grep -m1 -o "SYNTAX_ERROR" | head -n 1
|
||||
|
||||
echo "First auth method can't specify type if WITH keyword is not present"
|
||||
${CLICKHOUSE_CURL} -sS "${CLICKHOUSE_URL}" -d "CREATE USER ${user} $1 IDENTIFIED plaintext_password by '1'" 2>&1 | grep -m1 -o "SYNTAX_ERROR" | head -n 1
|
||||
|
||||
echo "RESET AUTHENTICATION METHODS TO NEW can only be used on alter statement"
|
||||
${CLICKHOUSE_CURL} -sS "${CLICKHOUSE_URL}" -d "CREATE USER ${user} $1 RESET AUTHENTICATION METHODS TO NEW" 2>&1 | grep -m1 -o "SYNTAX_ERROR" | head -n 1
|
||||
|
||||
echo "ADD NOT IDENTIFIED should result in syntax error"
|
||||
${CLICKHOUSE_CURL} -sS "${CLICKHOUSE_URL}" -d "ALTER USER ${user} $1 ADD NOT IDENTIFIED" 2>&1 | grep -m1 -o "SYNTAX_ERROR" | head -n 1
|
||||
|
||||
echo "RESET AUTHENTICATION METHODS TO NEW cannot be used along with [ADD] IDENTIFIED clauses"
|
||||
${CLICKHOUSE_CURL} -sS "${CLICKHOUSE_URL}" -d "ALTER USER ${user} $1 IDENTIFIED WITH plaintext_password by '1' RESET AUTHENTICATION METHODS TO NEW" 2>&1 | grep -m1 -o "SYNTAX_ERROR" | head -n 1
|
||||
|
||||
${CLICKHOUSE_CURL} -sS "${CLICKHOUSE_URL}" -d "DROP USER IF EXISTS ${user}"
|
||||
|
||||
}
|
||||
|
||||
test "ON CLUSTER test_shard_localhost"
|
@ -0,0 +1,2 @@
|
||||
CREATE USER u_03174_multiple_auth_show_create IDENTIFIED WITH plaintext_password, sha256_password, bcrypt_password, sha256_password
|
||||
CREATE USER u_03174_multiple_auth_show_create IDENTIFIED WITH sha256_password, plaintext_password, bcrypt_password, sha256_password
|
@ -0,0 +1,13 @@
|
||||
-- Tags: no-fasttest, no-parallel
|
||||
|
||||
-- Create user with mix both implicit and explicit auth type, starting with with
|
||||
CREATE USER u_03174_multiple_auth_show_create IDENTIFIED WITH plaintext_password by '1', by '2', bcrypt_password by '3', by '4';
|
||||
SHOW CREATE USER u_03174_multiple_auth_show_create;
|
||||
|
||||
DROP USER IF EXISTS u_03174_multiple_auth_show_create;
|
||||
|
||||
-- Create user with mix both implicit and explicit auth type, starting with by
|
||||
CREATE USER u_03174_multiple_auth_show_create IDENTIFIED by '1', plaintext_password by '2', bcrypt_password by '3', by '4';
|
||||
SHOW CREATE USER u_03174_multiple_auth_show_create;
|
||||
|
||||
DROP USER IF EXISTS u_03174_multiple_auth_show_create;
|
Loading…
Reference in New Issue
Block a user