Merge branch 'master' into keeper-some-improvement2

This commit is contained in:
Antonio Andelic 2024-09-17 10:35:38 +02:00
commit f3654b8fc8
184 changed files with 2040 additions and 946 deletions

View File

@ -4,15 +4,31 @@ description: Prints workflow debug info
runs:
using: "composite"
steps:
- name: Print envs
- name: Envs, event.json and contexts
shell: bash
run: |
echo "::group::Envs"
env
echo "::endgroup::"
- name: Print Event.json
shell: bash
run: |
echo "::group::Event.json"
echo '::group::Environment variables'
env | sort
echo '::endgroup::'
echo '::group::event.json'
python3 -m json.tool "$GITHUB_EVENT_PATH"
echo "::endgroup::"
echo '::endgroup::'
cat << 'EOF'
::group::github context
${{ toJSON(github) }}
::endgroup::
::group::env context
${{ toJSON(env) }}
::endgroup::
::group::runner context
${{ toJSON(runner) }}
::endgroup::
::group::job context
${{ toJSON(job) }}
::endgroup::
EOF

View File

@ -27,6 +27,8 @@ jobs:
clear-repository: true # to ensure correct digests
fetch-depth: 0 # to get version
filter: tree:0
- name: Debug Info
uses: ./.github/actions/debug
- name: Labels check
run: |
cd "$GITHUB_WORKSPACE/tests/ci"

View File

@ -33,6 +33,8 @@ jobs:
clear-repository: true
token: ${{secrets.ROBOT_CLICKHOUSE_COMMIT_TOKEN}}
fetch-depth: 0
- name: Debug Info
uses: ./.github/actions/debug
- name: Cherry pick
run: |
cd "$GITHUB_WORKSPACE/tests/ci"

View File

@ -56,13 +56,13 @@ jobs:
GH_TOKEN: ${{ secrets.ROBOT_CLICKHOUSE_COMMIT_TOKEN }}
runs-on: [self-hosted, release-maker]
steps:
- name: DebugInfo
uses: hmarr/debug-action@f7318c783045ac39ed9bb497e22ce835fdafbfe6
- name: Check out repository code
uses: ClickHouse/checkout@v1
with:
token: ${{secrets.ROBOT_CLICKHOUSE_COMMIT_TOKEN}}
fetch-depth: 0
- name: Debug Info
uses: ./.github/actions/debug
- name: Prepare Release Info
shell: bash
run: |

View File

@ -11,6 +11,7 @@ name: Build docker images
required: false
type: boolean
default: false
jobs:
DockerBuildAarch64:
runs-on: [self-hosted, style-checker-aarch64]

View File

@ -8,27 +8,28 @@ on: # yamllint disable-line rule:truthy
schedule:
- cron: '0 */6 * * *'
workflow_dispatch:
jobs:
RunConfig:
runs-on: [self-hosted, style-checker-aarch64]
outputs:
data: ${{ steps.runconfig.outputs.CI_DATA }}
steps:
- name: DebugInfo
uses: hmarr/debug-action@f7318c783045ac39ed9bb497e22ce835fdafbfe6
- name: Check out repository code
uses: ClickHouse/checkout@v1
with:
clear-repository: true # to ensure correct digests
fetch-depth: 0 # to get version
filter: tree:0
- name: Debug Info
uses: ./.github/actions/debug
- name: PrepareRunConfig
id: runconfig
run: |
echo "::group::configure CI run"
python3 "$GITHUB_WORKSPACE/tests/ci/ci.py" --configure --workflow "$GITHUB_WORKFLOW" --outfile ${{ runner.temp }}/ci_run_data.json
echo "::endgroup::"
echo "::group::CI run configure results"
python3 -m json.tool ${{ runner.temp }}/ci_run_data.json
echo "::endgroup::"

View File

@ -15,14 +15,14 @@ jobs:
outputs:
data: ${{ steps.runconfig.outputs.CI_DATA }}
steps:
- name: DebugInfo
uses: hmarr/debug-action@f7318c783045ac39ed9bb497e22ce835fdafbfe6
- name: Check out repository code
uses: ClickHouse/checkout@v1
with:
clear-repository: true # to ensure correct digests
fetch-depth: 0 # to get version
filter: tree:0
- name: Debug Info
uses: ./.github/actions/debug
- name: Merge sync PR
run: |
cd "$GITHUB_WORKSPACE/tests/ci"

View File

@ -14,14 +14,14 @@ jobs:
outputs:
data: ${{ steps.runconfig.outputs.CI_DATA }}
steps:
- name: DebugInfo
uses: hmarr/debug-action@f7318c783045ac39ed9bb497e22ce835fdafbfe6
- name: Check out repository code
uses: ClickHouse/checkout@v1
with:
clear-repository: true # to ensure correct digests
fetch-depth: 0 # to get a version
filter: tree:0
- name: Debug Info
uses: ./.github/actions/debug
- name: Cancel PR workflow
run: |
python3 "$GITHUB_WORKSPACE/tests/ci/ci.py" --cancel-previous-run

View File

@ -15,14 +15,14 @@ jobs:
outputs:
data: ${{ steps.runconfig.outputs.CI_DATA }}
steps:
- name: DebugInfo
uses: hmarr/debug-action@f7318c783045ac39ed9bb497e22ce835fdafbfe6
- name: Check out repository code
uses: ClickHouse/checkout@v1
with:
clear-repository: true # to ensure correct digests
fetch-depth: 0 # to get version
filter: tree:0
- name: Debug Info
uses: ./.github/actions/debug
- name: PrepareRunConfig
id: runconfig
run: |

View File

@ -25,14 +25,14 @@ jobs:
outputs:
data: ${{ steps.runconfig.outputs.CI_DATA }}
steps:
- name: DebugInfo
uses: hmarr/debug-action@f7318c783045ac39ed9bb497e22ce835fdafbfe6
- name: Check out repository code
uses: ClickHouse/checkout@v1
with:
clear-repository: true # to ensure correct digests
fetch-depth: 0 # to get a version
filter: tree:0
- name: Debug Info
uses: ./.github/actions/debug
- name: Cancel previous Sync PR workflow
run: |
python3 "$GITHUB_WORKSPACE/tests/ci/ci.py" --cancel-previous-run

View File

@ -24,6 +24,8 @@ jobs:
clear-repository: true # to ensure correct digests
fetch-depth: 0 # to get version
filter: tree:0
- name: Debug Info
uses: ./.github/actions/debug
- name: Labels check
run: |
cd "$GITHUB_WORKSPACE/tests/ci"

View File

@ -62,8 +62,6 @@ jobs:
env:
GITHUB_JOB_OVERRIDDEN: ${{inputs.test_name}}
steps:
- name: DebugInfo
uses: hmarr/debug-action@f7318c783045ac39ed9bb497e22ce835fdafbfe6
- name: Check out repository code
uses: ClickHouse/checkout@v1
with:
@ -72,6 +70,8 @@ jobs:
submodules: ${{inputs.submodules}}
fetch-depth: ${{inputs.checkout_depth}}
filter: tree:0
- name: Debug Info
uses: ./.github/actions/debug
- name: Set build envs
run: |
cat >> "$GITHUB_ENV" << 'EOF'

View File

@ -13,16 +13,17 @@ Here is a complete list of available database engines. Follow the links for more
- [Atomic](../../engines/database-engines/atomic.md)
- [MySQL](../../engines/database-engines/mysql.md)
- [Lazy](../../engines/database-engines/lazy.md)
- [MaterializedPostgreSQL](../../engines/database-engines/materialized-postgresql.md)
- [MaterializedMySQL](../../engines/database-engines/materialized-mysql.md)
- [Lazy](../../engines/database-engines/lazy.md)
- [MySQL](../../engines/database-engines/mysql.md)
- [PostgreSQL](../../engines/database-engines/postgresql.md)
- [MaterializedPostgreSQL](../../engines/database-engines/materialized-postgresql.md)
- [Replicated](../../engines/database-engines/replicated.md)
- [SQLite](../../engines/database-engines/sqlite.md)

View File

@ -107,6 +107,10 @@ The vector similarity index currently does not work with per-table, non-default
[here](https://github.com/ClickHouse/ClickHouse/pull/51325#issuecomment-1605920475)). If necessary, the value must be changed in config.xml.
:::
Vector index creation is known to be slow. To speed the process up, index creation can be parallelized. The maximum number of threads can be
configured using server configuration
setting [max_build_vector_similarity_index_thread_pool_size](../../../operations/server-configuration-parameters/settings.md#server_configuration_parameters_max_build_vector_similarity_index_thread_pool_size).
ANN indexes are built during column insertion and merge. As a result, `INSERT` and `OPTIMIZE` statements will be slower than for ordinary
tables. ANNIndexes are ideally used only with immutable or rarely changed data, respectively when are far more read requests than write
requests.

View File

@ -97,7 +97,7 @@ If you want to change the target table by using `ALTER`, we recommend disabling
- `_filename` - Name of the log file. Data type: `LowCardinality(String)`.
- `_offset` - Offset in the log file. Data type: `UInt64`.
Additional virtual columns when `kafka_handle_error_mode='stream'`:
Additional virtual columns when `handle_error_mode='stream'`:
- `_raw_record` - Raw record that couldn't be parsed successfully. Data type: `Nullable(String)`.
- `_error` - Exception message happened during failed parsing. Data type: `Nullable(String)`.

View File

@ -826,17 +826,17 @@ Result:
## JSONAsObject {#jsonasobject}
In this format, a single JSON object is interpreted as a single [Object('json')](/docs/en/sql-reference/data-types/json.md) value. If the input has several JSON objects (comma separated), they are interpreted as separate rows. If the input data is enclosed in square brackets, it is interpreted as an array of JSONs.
In this format, a single JSON object is interpreted as a single [JSON](/docs/en/sql-reference/data-types/newjson.md) value. If the input has several JSON objects (comma separated), they are interpreted as separate rows. If the input data is enclosed in square brackets, it is interpreted as an array of JSONs.
This format can only be parsed for a table with a single field of type [Object('json')](/docs/en/sql-reference/data-types/json.md). The remaining columns must be set to [DEFAULT](/docs/en/sql-reference/statements/create/table.md/#default) or [MATERIALIZED](/docs/en/sql-reference/statements/create/table.md/#materialized).
This format can only be parsed for a table with a single field of type [JSON](/docs/en/sql-reference/data-types/newjson.md). The remaining columns must be set to [DEFAULT](/docs/en/sql-reference/statements/create/table.md/#default) or [MATERIALIZED](/docs/en/sql-reference/statements/create/table.md/#materialized).
**Examples**
Query:
``` sql
SET allow_experimental_object_type = 1;
CREATE TABLE json_as_object (json Object('json')) ENGINE = Memory;
SET allow_experimental_json_type = 1;
CREATE TABLE json_as_object (json JSON) ENGINE = Memory;
INSERT INTO json_as_object (json) FORMAT JSONAsObject {"foo":{"bar":{"x":"y"},"baz":1}},{},{"any json stucture":1}
SELECT * FROM json_as_object FORMAT JSONEachRow;
```
@ -844,9 +844,9 @@ SELECT * FROM json_as_object FORMAT JSONEachRow;
Result:
``` response
{"json":{"any json stucture":0,"foo":{"bar":{"x":"y"},"baz":1}}}
{"json":{"any json stucture":0,"foo":{"bar":{"x":""},"baz":0}}}
{"json":{"any json stucture":1,"foo":{"bar":{"x":""},"baz":0}}}
{"json":{"foo":{"bar":{"x":"y"},"baz":"1"}}}
{"json":{}}
{"json":{"any json stucture":"1"}}
```
**An array of JSON objects**
@ -854,35 +854,34 @@ Result:
Query:
``` sql
SET allow_experimental_object_type = 1;
CREATE TABLE json_square_brackets (field Object('json')) ENGINE = Memory;
SET allow_experimental_json_type = 1;
CREATE TABLE json_square_brackets (field JSON) ENGINE = Memory;
INSERT INTO json_square_brackets FORMAT JSONAsObject [{"id": 1, "name": "name1"}, {"id": 2, "name": "name2"}];
SELECT * FROM json_square_brackets FORMAT JSONEachRow;
```
Result:
```response
{"field":{"id":1,"name":"name1"}}
{"field":{"id":2,"name":"name2"}}
{"field":{"id":"1","name":"name1"}}
{"field":{"id":"2","name":"name2"}}
```
**Columns with default values**
```sql
SET allow_experimental_object_type = 1;
CREATE TABLE json_as_object (json Object('json'), time DateTime MATERIALIZED now()) ENGINE = Memory;
SET allow_experimental_json_type = 1;
CREATE TABLE json_as_object (json JSON, time DateTime MATERIALIZED now()) ENGINE = Memory;
INSERT INTO json_as_object (json) FORMAT JSONAsObject {"foo":{"bar":{"x":"y"},"baz":1}};
INSERT INTO json_as_object (json) FORMAT JSONAsObject {};
INSERT INTO json_as_object (json) FORMAT JSONAsObject {"any json stucture":1}
SELECT * FROM json_as_object FORMAT JSONEachRow
SELECT time, json FROM json_as_object FORMAT JSONEachRow
```
```resonse
{"json":{"any json stucture":0,"foo":{"bar":{"x":"y"},"baz":1}},"time":"2024-07-25 17:02:45"}
{"json":{"any json stucture":0,"foo":{"bar":{"x":""},"baz":0}},"time":"2024-07-25 17:02:47"}
{"json":{"any json stucture":1,"foo":{"bar":{"x":""},"baz":0}},"time":"2024-07-25 17:02:50"}
{"time":"2024-09-16 12:18:10","json":{}}
{"time":"2024-09-16 12:18:13","json":{"any json stucture":"1"}}
{"time":"2024-09-16 12:18:08","json":{"foo":{"bar":{"x":"y"},"baz":"1"}}}
```
## JSONCompact {#jsoncompact}

View File

@ -491,6 +491,14 @@ Type: Double
Default: 0.9
## max_build_vector_similarity_index_thread_pool_size {#server_configuration_parameters_max_build_vector_similarity_index_thread_pool_size}
The maximum number of threads to use for building vector indexes. 0 means all cores.
Type: UInt64
Default: 16
## cgroups_memory_usage_observer_wait_time
Interval in seconds during which the server's maximum allowed memory consumption is adjusted by the corresponding threshold in cgroups. (see
@ -3142,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

View File

@ -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
```

View File

@ -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:

View File

@ -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>())
{

View File

@ -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;
}
}

View File

@ -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);

View File

@ -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));
}

View File

@ -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");

View File

@ -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;

View File

@ -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;
}

View File

@ -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);

View File

@ -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;

View File

@ -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";

View File

@ -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);

View File

@ -178,6 +178,9 @@
M(ObjectStorageAzureThreads, "Number of threads in the AzureObjectStorage thread pool.") \
M(ObjectStorageAzureThreadsActive, "Number of threads in the AzureObjectStorage thread pool running a task.") \
M(ObjectStorageAzureThreadsScheduled, "Number of queued or active jobs in the AzureObjectStorage thread pool.") \
M(BuildVectorSimilarityIndexThreads, "Number of threads in the build vector similarity index thread pool.") \
M(BuildVectorSimilarityIndexThreadsActive, "Number of threads in the build vector similarity index thread pool running a task.") \
M(BuildVectorSimilarityIndexThreadsScheduled, "Number of queued or active jobs in the build vector similarity index thread pool.") \
\
M(DiskPlainRewritableAzureDirectoryMapSize, "Number of local-to-remote path entries in the 'plain_rewritable' in-memory map for AzureObjectStorage.") \
M(DiskPlainRewritableLocalDirectoryMapSize, "Number of local-to-remote path entries in the 'plain_rewritable' in-memory map for LocalObjectStorage.") \

View File

@ -63,6 +63,7 @@ static struct InitFiu
REGULAR(keepermap_fail_drop_data) \
REGULAR(lazy_pipe_fds_fail_close) \
PAUSEABLE(infinite_sleep) \
PAUSEABLE(stop_moving_part_before_swap_with_active) \
namespace FailPoints

View File

@ -2535,6 +2535,7 @@ process(const Coordination::ZooKeeperMultiRequest & zk_request, Storage & storag
response->responses[i]->error = failed_multi->error_codes[i];
}
response->error = failed_multi->global_error;
return response;
}
@ -3036,7 +3037,19 @@ void KeeperStorage<Container>::preprocessRequest(
{
if (check_acl && !checkAuth(concrete_zk_request, *this, session_id, false))
{
new_deltas.emplace_back(new_last_zxid, Coordination::Error::ZNOAUTH);
/// Multi requests handle failures using FailedMultiDelta
if (zk_request->getOpNum() == Coordination::OpNum::Multi || zk_request->getOpNum() == Coordination::OpNum::MultiRead)
{
const auto & multi_request = dynamic_cast<const Coordination::ZooKeeperMultiRequest &>(*zk_request);
std::vector<Coordination::Error> response_errors;
response_errors.resize(multi_request.requests.size(), Coordination::Error::ZOK);
new_deltas.emplace_back(
new_last_zxid, KeeperStorage<Container>::FailedMultiDelta{std::move(response_errors), Coordination::Error::ZNOAUTH});
}
else
{
new_deltas.emplace_back(new_last_zxid, Coordination::Error::ZNOAUTH);
}
return;
}

View File

@ -383,6 +383,7 @@ public:
struct FailedMultiDelta
{
std::vector<Coordination::Error> error_codes;
Coordination::Error global_error{Coordination::Error::ZOK};
};
// Denotes end of a subrequest in multi request

View File

@ -2281,6 +2281,62 @@ TYPED_TEST(CoordinationTest, TestPreprocessWhenCloseSessionIsPrecommitted)
}
}
TYPED_TEST(CoordinationTest, TestMultiRequestWithNoAuth)
{
using namespace Coordination;
using namespace DB;
ChangelogDirTest snapshots("./snapshots");
this->setSnapshotDirectory("./snapshots");
using Storage = typename TestFixture::Storage;
ChangelogDirTest rocks("./rocksdb");
this->setRocksDBDirectory("./rocksdb");
ResponsesQueue queue(std::numeric_limits<size_t>::max());
SnapshotsQueue snapshots_queue{1};
int64_t session_without_auth = 1;
int64_t session_with_auth = 2;
size_t term = 0;
auto state_machine = std::make_shared<KeeperStateMachine<Storage>>(queue, snapshots_queue, this->keeper_context, nullptr);
state_machine->init();
auto & storage = state_machine->getStorageUnsafe();
auto auth_req = std::make_shared<ZooKeeperAuthRequest>();
auth_req->scheme = "digest";
auth_req->data = "test_user:test_password";
// Add auth data to the session
auto auth_entry = getLogEntryFromZKRequest(term, session_with_auth, state_machine->getNextZxid(), auth_req);
state_machine->pre_commit(1, auth_entry->get_buf());
state_machine->commit(1, auth_entry->get_buf());
std::string node_with_acl = "/node_with_acl";
{
auto create_req = std::make_shared<ZooKeeperCreateRequest>();
create_req->path = node_with_acl;
create_req->data = "notmodified";
create_req->acls = {{.permissions = ACL::Read, .scheme = "auth", .id = ""}};
auto create_entry = getLogEntryFromZKRequest(term, session_with_auth, state_machine->getNextZxid(), create_req);
state_machine->pre_commit(3, create_entry->get_buf());
state_machine->commit(3, create_entry->get_buf());
ASSERT_TRUE(storage.container.contains(node_with_acl));
}
Requests ops;
ops.push_back(zkutil::makeSetRequest(node_with_acl, "modified", -1));
ops.push_back(zkutil::makeCheckRequest("/nonexistentnode", -1));
auto multi_req = std::make_shared<ZooKeeperMultiRequest>(ops, ACLs{});
auto multi_entry = getLogEntryFromZKRequest(term, session_without_auth, state_machine->getNextZxid(), multi_req);
state_machine->pre_commit(4, multi_entry->get_buf());
state_machine->commit(4, multi_entry->get_buf());
auto node_it = storage.container.find(node_with_acl);
ASSERT_FALSE(node_it == storage.container.end());
ASSERT_TRUE(node_it->value.getData() == "notmodified");
}
TYPED_TEST(CoordinationTest, TestSetACLWithAuthSchemeForAclWhenAuthIsPrecommitted)
{
using namespace Coordination;

View File

@ -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");
}
};
}

View File

@ -50,7 +50,7 @@ namespace DB
M(UInt32, asynchronous_heavy_metrics_update_period_s, 120, "Period in seconds for updating heavy asynchronous metrics.", 0) \
M(String, default_database, "default", "Default database name.", 0) \
M(String, tmp_policy, "", "Policy for storage with temporary data.", 0) \
M(UInt64, max_temporary_data_on_disk_size, 0, "The maximum amount of storage that could be used for external aggregation, joins or sorting., ", 0) \
M(UInt64, max_temporary_data_on_disk_size, 0, "The maximum amount of storage that could be used for external aggregation, joins or sorting.", 0) \
M(String, temporary_data_in_cache, "", "Cache disk name for temporary data.", 0) \
M(UInt64, aggregate_function_group_array_max_element_size, 0xFFFFFF, "Max array element size in bytes for groupArray function. This limit is checked at serialization and help to avoid large state size.", 0) \
M(GroupArrayActionWhenLimitReached, aggregate_function_group_array_action_when_limit_is_reached, GroupArrayActionWhenLimitReached::THROW, "Action to execute when max array element size is exceeded in groupArray: `throw` exception, or `discard` extra values", 0) \
@ -65,6 +65,7 @@ namespace DB
M(UInt64, async_insert_threads, 16, "Maximum number of threads to actually parse and insert data in background. Zero means asynchronous mode is disabled", 0) \
M(Bool, async_insert_queue_flush_on_shutdown, true, "If true queue of asynchronous inserts is flushed on graceful shutdown", 0) \
M(Bool, ignore_empty_sql_security_in_create_view_query, true, "If true, ClickHouse doesn't write defaults for empty SQL security statement in CREATE VIEW queries. This setting is only necessary for the migration period and will become obsolete in 24.4", 0) \
M(UInt64, max_build_vector_similarity_index_thread_pool_size, 16, "The maximum number of threads to use to build vector similarity indexes. 0 means all cores.", 0) \
\
/* Database Catalog */ \
M(UInt64, database_atomic_delay_before_drop_table_sec, 8 * 60, "The delay during which a dropped table can be restored using the UNDROP statement. If DROP TABLE ran with a SYNC modifier, the setting is ignored.", 0) \
@ -118,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) \
\

View File

@ -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) \

View File

@ -4,6 +4,7 @@
#include <Common/ZooKeeper/KeeperException.h>
#include <Core/ServerUUID.h>
#include <Core/Settings.h>
#include <base/sleep.h>
#include <filesystem>
namespace fs = std::filesystem;
@ -249,6 +250,8 @@ String DatabaseReplicatedDDLWorker::enqueueQueryImpl(const ZooKeeperPtr & zookee
}
else if (code != Coordination::Error::ZNODEEXISTS)
zkutil::KeeperMultiException::check(code, ops, res);
sleepForMilliseconds(50);
}
if (counter_path.empty())

View File

@ -50,13 +50,6 @@ private:
return executeNonconstant(input);
}
[[maybe_unused]] String toString() const
{
WriteBufferFromOwnString buf;
buf << "format:" << format << ", rows:" << rows << ", is_literal:" << is_literal << ", input:" << input.dumpStructure() << "\n";
return buf.str();
}
private:
ColumnWithTypeAndName executeLiteral(std::string_view literal) const
{
@ -231,9 +224,7 @@ public:
const auto & instruction = instructions[i];
try
{
// std::cout << "instruction[" << i << "]:" << instructions[i].toString() << std::endl;
concat_args[i] = instruction.execute();
// std::cout << "concat_args[" << i << "]:" << concat_args[i].dumpStructure() << std::endl;
}
catch (const fmt::v9::format_error & e)
{
@ -358,7 +349,14 @@ private:
REGISTER_FUNCTION(Printf)
{
factory.registerFunction<FunctionPrintf>();
factory.registerFunction<FunctionPrintf>(
FunctionDocumentation{.description=R"(
The `printf` function formats the given string with the values (strings, integers, floating-points etc.) listed in the arguments, similar to printf function in C++.
The format string can contain format specifiers starting with `%` character.
Anything not contained in `%` and the following format specifier is considered literal text and copied verbatim into the output.
Literal `%` character can be escaped by `%%`.)", .examples{{"sum", "select printf('%%%s %s %d', 'Hello', 'World', 2024);", "%Hello World 2024"}}, .categories{"String"}
});
}
}

View File

@ -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)

View File

@ -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;

View File

@ -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)
{

View File

@ -10,6 +10,7 @@
#include <Common/SensitiveDataMasker.h>
#include <Common/Macros.h>
#include <Common/EventNotifier.h>
#include <Common/getNumberOfPhysicalCPUCores.h>
#include <Common/Stopwatch.h>
#include <Common/formatReadable.h>
#include <Common/Throttler.h>
@ -121,7 +122,6 @@
#include <Interpreters/InterpreterSelectWithUnionQuery.h>
#include <base/defines.h>
namespace fs = std::filesystem;
namespace ProfileEvents
@ -164,6 +164,9 @@ namespace CurrentMetrics
extern const Metric TablesLoaderForegroundThreadsActive;
extern const Metric TablesLoaderForegroundThreadsScheduled;
extern const Metric IOWriterThreadsScheduled;
extern const Metric BuildVectorSimilarityIndexThreads;
extern const Metric BuildVectorSimilarityIndexThreadsActive;
extern const Metric BuildVectorSimilarityIndexThreadsScheduled;
extern const Metric AttachedTable;
extern const Metric AttachedView;
extern const Metric AttachedDictionary;
@ -297,6 +300,8 @@ struct ContextSharedPart : boost::noncopyable
mutable std::unique_ptr<ThreadPool> load_marks_threadpool; /// Threadpool for loading marks cache.
mutable OnceFlag prefetch_threadpool_initialized;
mutable std::unique_ptr<ThreadPool> prefetch_threadpool; /// Threadpool for loading marks cache.
mutable OnceFlag build_vector_similarity_index_threadpool_initialized;
mutable std::unique_ptr<ThreadPool> build_vector_similarity_index_threadpool; /// Threadpool for vector-similarity index creation.
mutable UncompressedCachePtr index_uncompressed_cache TSA_GUARDED_BY(mutex); /// The cache of decompressed blocks for MergeTree indices.
mutable QueryCachePtr query_cache TSA_GUARDED_BY(mutex); /// Cache of query results.
mutable MarkCachePtr index_mark_cache TSA_GUARDED_BY(mutex); /// Cache of marks in compressed files of MergeTree indices.
@ -3297,6 +3302,21 @@ size_t Context::getPrefetchThreadpoolSize() const
return config.getUInt(".prefetch_threadpool_pool_size", 100);
}
ThreadPool & Context::getBuildVectorSimilarityIndexThreadPool() const
{
callOnce(shared->build_vector_similarity_index_threadpool_initialized, [&] {
size_t pool_size = shared->server_settings.max_build_vector_similarity_index_thread_pool_size > 0
? shared->server_settings.max_build_vector_similarity_index_thread_pool_size
: getNumberOfPhysicalCPUCores();
shared->build_vector_similarity_index_threadpool = std::make_unique<ThreadPool>(
CurrentMetrics::BuildVectorSimilarityIndexThreads,
CurrentMetrics::BuildVectorSimilarityIndexThreadsActive,
CurrentMetrics::BuildVectorSimilarityIndexThreadsScheduled,
pool_size);
});
return *shared->build_vector_similarity_index_threadpool;
}
BackgroundSchedulePool & Context::getBufferFlushSchedulePool() const
{
callOnce(shared->buffer_flush_schedule_pool_initialized, [&] {

View File

@ -1097,6 +1097,8 @@ public:
/// and make a prefetch by putting a read task to threadpoolReader.
size_t getPrefetchThreadpoolSize() const;
ThreadPool & getBuildVectorSimilarityIndexThreadPool() const;
/// Settings for MergeTree background tasks stored in config.xml
BackgroundTaskSchedulingSettings getBackgroundProcessingTaskSchedulingSettings() const;
BackgroundTaskSchedulingSettings getBackgroundMoveTaskSchedulingSettings() const;

View File

@ -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;

View File

@ -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;

View File

@ -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));

View File

@ -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);
};
}

View File

@ -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())

View File

@ -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 : "");
}
}

View File

@ -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;

View File

@ -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);

View File

@ -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") \

View File

@ -74,7 +74,8 @@ private:
findMySQLFunctionSecretArguments();
}
else if ((function.name == "s3") || (function.name == "cosn") || (function.name == "oss") ||
(function.name == "deltaLake") || (function.name == "hudi") || (function.name == "iceberg"))
(function.name == "deltaLake") || (function.name == "hudi") || (function.name == "iceberg") ||
(function.name == "gcs"))
{
/// s3('url', 'aws_access_key_id', 'aws_secret_access_key', ...)
findS3FunctionSecretArguments(/* is_cluster_function= */ false);

View File

@ -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"
}
})));

View File

@ -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

View File

@ -17,9 +17,7 @@ void WriteBufferFromHTTPServerResponse::startSendHeaders()
{
headers_started_sending = true;
if (response.getChunkedTransferEncoding())
setChunked();
else if (response.getContentLength() == Poco::Net::HTTPMessage::UNKNOWN_CONTENT_LENGTH)
if (!response.getChunkedTransferEncoding() && response.getContentLength() == Poco::Net::HTTPMessage::UNKNOWN_CONTENT_LENGTH)
{
/// In case there is no Content-Length we cannot use keep-alive,
/// since there is no way to know when the server send all the
@ -134,6 +132,8 @@ WriteBufferFromHTTPServerResponse::WriteBufferFromHTTPServerResponse(
, response(response_)
, is_http_method_head(is_http_method_head_)
{
if (response.getChunkedTransferEncoding())
setChunked();
}

View File

@ -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;

View File

@ -90,15 +90,15 @@ static inline void trySendExceptionToClient(
void StaticRequestHandler::handleRequest(HTTPServerRequest & request, HTTPServerResponse & response, const ProfileEvents::Event & /*write_event*/)
{
applyHTTPResponseHeaders(response, http_response_headers_override);
if (request.getVersion() == Poco::Net::HTTPServerRequest::HTTP_1_1)
response.setChunkedTransferEncoding(true);
auto out = responseWriteBuffer(request, response);
try
{
applyHTTPResponseHeaders(response, http_response_headers_override);
if (request.getVersion() == Poco::Net::HTTPServerRequest::HTTP_1_1)
response.setChunkedTransferEncoding(true);
/// Workaround. Poco does not detect 411 Length Required case.
if (request.getMethod() == Poco::Net::HTTPRequest::HTTP_POST && !request.getChunkedTransferEncoding() && !request.hasContentLength())
throw Exception(ErrorCodes::HTTP_LENGTH_REQUIRED,

View File

@ -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)

View File

@ -5,9 +5,11 @@
#include <Columns/ColumnArray.h>
#include <Common/BitHelpers.h>
#include <Common/formatReadable.h>
#include <Common/getNumberOfPhysicalCPUCores.h>
#include <Common/logger_useful.h>
#include <Common/typeid_cast.h>
#include <Core/Field.h>
#include <Core/ServerSettings.h>
#include <DataTypes/DataTypeArray.h>
#include <IO/ReadHelpers.h>
#include <IO/WriteHelpers.h>
@ -29,7 +31,6 @@ namespace DB
namespace ErrorCodes
{
extern const int CANNOT_ALLOCATE_MEMORY;
extern const int FORMAT_VERSION_TOO_OLD;
extern const int ILLEGAL_COLUMN;
extern const int INCORRECT_DATA;
@ -131,8 +132,7 @@ void USearchIndexWithSerialization::deserialize(ReadBuffer & istr)
/// See the comment in MergeTreeIndexGranuleVectorSimilarity::deserializeBinary why we throw here
throw Exception(ErrorCodes::INCORRECT_DATA, "Could not load vector similarity index. Please drop the index and create it again. Error: {}", String(result.error.release()));
if (!try_reserve(limits()))
throw Exception(ErrorCodes::CANNOT_ALLOCATE_MEMORY, "Could not reserve memory for usearch index");
try_reserve(limits());
}
USearchIndexWithSerialization::Statistics USearchIndexWithSerialization::getStatistics() const
@ -270,20 +270,49 @@ void updateImpl(const ColumnArray * column_array, const ColumnArray::Offsets & c
throw Exception(ErrorCodes::INCORRECT_DATA, "All arrays in column with vector similarity index must have equal length");
/// Reserving space is mandatory
if (!index->try_reserve(roundUpToPowerOfTwoOrZero(index->size() + rows)))
throw Exception(ErrorCodes::CANNOT_ALLOCATE_MEMORY, "Could not reserve memory for vector similarity index");
size_t max_thread_pool_size = Context::getGlobalContextInstance()->getServerSettings().max_build_vector_similarity_index_thread_pool_size;
if (max_thread_pool_size == 0)
max_thread_pool_size = getNumberOfPhysicalCPUCores();
unum::usearch::index_limits_t limits(roundUpToPowerOfTwoOrZero(index->size() + rows), max_thread_pool_size);
index->reserve(limits);
for (size_t row = 0; row < rows; ++row)
/// Vector index creation is slooooow. Add the new rows in parallel. The threadpool is global to avoid oversubscription when multiple
/// indexes are build simultaneously (e.g. multiple merges run at the same time).
auto & thread_pool = Context::getGlobalContextInstance()->getBuildVectorSimilarityIndexThreadPool();
auto add_vector_to_index = [&](USearchIndex::vector_key_t key, size_t row, ThreadGroupPtr thread_group)
{
if (auto result = index->add(static_cast<USearchIndex::vector_key_t>(index->size()), &column_array_data_float_data[column_array_offsets[row - 1]]); !result)
SCOPE_EXIT_SAFE(
if (thread_group)
CurrentThread::detachFromGroupIfNotDetached();
);
if (thread_group)
CurrentThread::attachToGroupIfDetached(thread_group);
/// add is thread-safe
if (auto result = index->add(key, &column_array_data_float_data[column_array_offsets[row - 1]]); !result)
{
throw Exception(ErrorCodes::INCORRECT_DATA, "Could not add data to vector similarity index. Error: {}", String(result.error.release()));
}
else
{
ProfileEvents::increment(ProfileEvents::USearchAddCount);
ProfileEvents::increment(ProfileEvents::USearchAddVisitedMembers, result.visited_members);
ProfileEvents::increment(ProfileEvents::USearchAddComputedDistances, result.computed_distances);
}
};
size_t index_size = index->size();
for (size_t row = 0; row < rows; ++row)
{
auto key = static_cast<USearchIndex::vector_key_t>(index_size + row);
auto task = [group = CurrentThread::getGroup(), &add_vector_to_index, key, row] { add_vector_to_index(key, row, group); };
thread_pool.scheduleOrThrowOnError(task);
}
thread_pool.wait();
}
}

View File

@ -1,6 +1,7 @@
#include <Storages/MergeTree/MergeTreeData.h>
#include <Storages/MergeTree/MergeTreePartsMover.h>
#include <Storages/MergeTree/MergeTreeSettings.h>
#include <Common/FailPoint.h>
#include <Common/logger_useful.h>
#include <set>
@ -15,6 +16,11 @@ namespace ErrorCodes
extern const int DIRECTORY_ALREADY_EXISTS;
}
namespace FailPoints
{
extern const char stop_moving_part_before_swap_with_active[];
}
namespace
{
@ -226,6 +232,7 @@ MergeTreePartsMover::TemporaryClonedPart MergeTreePartsMover::clonePart(const Me
cloned_part.temporary_directory_lock = data->getTemporaryPartDirectoryHolder(part->name);
MutableDataPartStoragePtr cloned_part_storage;
bool preserve_blobs = false;
if (disk->supportZeroCopyReplication() && settings->allow_remote_fs_zero_copy_replication)
{
/// Try zero-copy replication and fallback to default copy if it's not possible
@ -253,6 +260,7 @@ MergeTreePartsMover::TemporaryClonedPart MergeTreePartsMover::clonePart(const Me
if (zero_copy_part)
{
/// FIXME for some reason we cannot just use this part, we have to re-create it through MergeTreeDataPartBuilder
preserve_blobs = true;
zero_copy_part->is_temp = false; /// Do not remove it in dtor
cloned_part_storage = zero_copy_part->getDataPartStoragePtr();
}
@ -272,7 +280,17 @@ MergeTreePartsMover::TemporaryClonedPart MergeTreePartsMover::clonePart(const Me
cloned_part.part = std::move(builder).withPartFormatFromDisk().build();
LOG_TRACE(log, "Part {} was cloned to {}", part->name, cloned_part.part->getDataPartStorage().getFullPath());
cloned_part.part->is_temp = data->allowRemoveStaleMovingParts();
cloned_part.part->is_temp = false;
if (data->allowRemoveStaleMovingParts())
{
cloned_part.part->is_temp = true;
/// Setting it in case connection to zookeeper is lost while moving
/// Otherwise part might be stuck in the moving directory due to the KEEPER_EXCEPTION in part's destructor
if (preserve_blobs)
cloned_part.part->remove_tmp_policy = IMergeTreeDataPart::BlobsRemovalPolicyForTemporaryParts::PRESERVE_BLOBS;
else
cloned_part.part->remove_tmp_policy = IMergeTreeDataPart::BlobsRemovalPolicyForTemporaryParts::REMOVE_BLOBS;
}
cloned_part.part->loadColumnsChecksumsIndexes(true, true);
cloned_part.part->loadVersionMetadata();
cloned_part.part->modification_time = cloned_part.part->getDataPartStorage().getLastModified().epochTime();
@ -282,6 +300,8 @@ MergeTreePartsMover::TemporaryClonedPart MergeTreePartsMover::clonePart(const Me
void MergeTreePartsMover::swapClonedPart(TemporaryClonedPart & cloned_part) const
{
/// Used to get some stuck parts in the moving directory by stopping moves while pause is active
FailPointInjection::pauseFailPoint(FailPoints::stop_moving_part_before_swap_with_active);
if (moves_blocker.isCancelled())
throw Exception(ErrorCodes::ABORTED, "Cancelled moving parts.");

View File

@ -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);
}
}

View File

@ -42,6 +42,7 @@
<multi_read>1</multi_read>
<check_not_exists>1</check_not_exists>
<create_if_not_exists>1</create_if_not_exists>
<remove_recursive>1</remove_recursive>
</feature_flags>
</keeper_server>
</clickhouse>

View File

@ -105,7 +105,7 @@ setup_logs_replication
clickhouse-client --query "SHOW DATABASES"
clickhouse-client --query "CREATE DATABASE datasets"
clickhouse-client --multiquery < /repo/tests/docker_scripts/create.sql
clickhouse-client < /repo/tests/docker_scripts/create.sql
clickhouse-client --query "SHOW TABLES FROM datasets"
if [[ -n "$USE_DATABASE_REPLICATED" ]] && [[ "$USE_DATABASE_REPLICATED" -eq 1 ]]; then

View File

@ -62,7 +62,7 @@ start_server
setup_logs_replication
clickhouse-client --query "CREATE DATABASE datasets"
clickhouse-client --multiquery < /repo/tests/docker_scripts/create.sql
clickhouse-client < /repo/tests/docker_scripts/create.sql
clickhouse-client --query "SHOW TABLES FROM datasets"
clickhouse-client --query "CREATE DATABASE IF NOT EXISTS test"

View File

@ -64,6 +64,7 @@ function configure()
randomize_config_boolean_value multi_read keeper_port
randomize_config_boolean_value check_not_exists keeper_port
randomize_config_boolean_value create_if_not_exists keeper_port
randomize_config_boolean_value remove_recursive keeper_port
fi
sudo chown clickhouse /etc/clickhouse-server/config.d/keeper_port.xml

View File

@ -89,7 +89,6 @@ class Client:
command = self.command[:]
if stdin is None:
command += ["--multiquery"]
stdin = sql
else:
command += ["--query", sql]

View File

@ -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

View File

@ -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"]
)

View File

@ -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"

View File

@ -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"

View File

@ -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")

View File

@ -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']",
"[]",
"[]",

View File

@ -393,6 +393,7 @@ def test_table_functions():
f"azureBlobStorageCluster('test_shard_localhost', named_collection_2, connection_string = '{azure_conn_string}', container = 'cont', blob_path = 'test_simple_16.csv', format = 'CSV')",
f"azureBlobStorageCluster('test_shard_localhost', named_collection_2, storage_account_url = '{azure_storage_account_url}', container = 'cont', blob_path = 'test_simple_17.csv', account_name = '{azure_account_name}', account_key = '{azure_account_key}')",
f"iceberg('http://minio1:9001/root/data/test11.csv.gz', 'minio', '{password}')",
f"gcs('http://minio1:9001/root/data/test11.csv.gz', 'minio', '{password}')",
]
def make_test_case(i):

View File

@ -0,0 +1,3 @@
<clickhouse>
<max_authentication_methods_per_user>2</max_authentication_methods_per_user>
</clickhouse>

View File

@ -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")

View File

@ -0,0 +1,46 @@
<clickhouse>
<remote_servers>
<cluster>
<shard>
<replica>
<host>ch1</host>
<port>9000</port>
</replica>
</shard>
</cluster>
</remote_servers>
<macros>
<shard>01</shard>
</macros>
<storage_configuration>
<disks>
<s3>
<type>s3</type>
<endpoint>http://minio1:9001/root/data/</endpoint>
<access_key_id>minio</access_key_id>
<secret_access_key>minio123</secret_access_key>
</s3>
</disks>
<policies>
<s3>
<volumes>
<default>
<disk>default</disk>
<perform_ttl_move_on_insert>False</perform_ttl_move_on_insert>
</default>
<s3>
<disk>s3</disk>
<perform_ttl_move_on_insert>False</perform_ttl_move_on_insert>
</s3>
</volumes>
<move_factor>0.0</move_factor>
</s3>
</policies>
</storage_configuration>
<merge_tree>
<allow_remote_fs_zero_copy_replication>true</allow_remote_fs_zero_copy_replication>
<storage_policy>s3</storage_policy>
</merge_tree>
<allow_remove_stale_moving_parts>true</allow_remove_stale_moving_parts>
</clickhouse>

View File

@ -0,0 +1,117 @@
from pathlib import Path
import time
import pytest
from helpers.cluster import ClickHouseCluster
cluster = ClickHouseCluster(__file__)
ch1 = cluster.add_instance(
"ch1",
main_configs=[
"config.xml",
],
macros={"replica": "node1"},
with_zookeeper=True,
with_minio=True,
)
DATABASE_NAME = "stale_moving_parts"
@pytest.fixture(scope="module")
def started_cluster():
try:
cluster.start()
yield cluster
finally:
cluster.shutdown()
def q(node, query):
return node.query(database=DATABASE_NAME, sql=query)
# .../disks/s3/store/
def get_table_path(node, table):
return (
node.query(
sql=f"SELECT data_paths FROM system.tables WHERE table = '{table}' and database = '{DATABASE_NAME}' LIMIT 1"
)
.strip('"\n[]')
.split(",")[1]
.strip("'")
)
def exec(node, cmd, path):
return node.exec_in_container(
[
"bash",
"-c",
f"{cmd} {path}",
]
)
def wait_part_is_stuck(node, table_moving_path, moving_part):
num_tries = 5
while q(node, "SELECT part_name FROM system.moves").strip() != moving_part:
if num_tries == 0:
raise Exception("Part has not started to move")
num_tries -= 1
time.sleep(1)
num_tries = 5
while exec(node, "ls", table_moving_path).strip() != moving_part:
if num_tries == 0:
raise Exception("Part is not stuck in the moving directory")
num_tries -= 1
time.sleep(1)
def wait_zookeeper_node_to_start(zk_nodes, timeout=60):
start = time.time()
while time.time() - start < timeout:
try:
for instance in zk_nodes:
conn = cluster.get_kazoo_client(instance)
conn.get_children("/")
print("All instances of ZooKeeper started")
return
except Exception as ex:
print(("Can't connect to ZooKeeper " + str(ex)))
time.sleep(0.5)
def test_remove_stale_moving_parts_without_zookeeper(started_cluster):
ch1.query(f"CREATE DATABASE IF NOT EXISTS {DATABASE_NAME}")
q(
ch1,
"CREATE TABLE test_remove ON CLUSTER cluster ( id UInt32 ) ENGINE ReplicatedMergeTree() ORDER BY id;",
)
table_moving_path = Path(get_table_path(ch1, "test_remove")) / "moving"
q(ch1, "SYSTEM ENABLE FAILPOINT stop_moving_part_before_swap_with_active")
q(ch1, "INSERT INTO test_remove SELECT number FROM numbers(100);")
moving_part = "all_0_0_0"
move_response = ch1.get_query_request(
sql=f"ALTER TABLE test_remove MOVE PART '{moving_part}' TO DISK 's3'",
database=DATABASE_NAME,
)
wait_part_is_stuck(ch1, table_moving_path, moving_part)
cluster.stop_zookeeper_nodes(["zoo1", "zoo2", "zoo3"])
# Stop moves in case table is not read-only yet
q(ch1, "SYSTEM STOP MOVES")
q(ch1, "SYSTEM DISABLE FAILPOINT stop_moving_part_before_swap_with_active")
assert "Cancelled moving parts" in move_response.get_error()
assert exec(ch1, "ls", table_moving_path).strip() == ""
cluster.start_zookeeper_nodes(["zoo1", "zoo2", "zoo3"])
wait_zookeeper_node_to_start(["zoo1", "zoo2", "zoo3"])
q(ch1, "SYSTEM START MOVES")
q(ch1, f"DROP TABLE test_remove")

View File

@ -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'",

View File

@ -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 (

View File

@ -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")

View File

@ -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")

View File

@ -427,7 +427,7 @@ do
done
# for each query run, prepare array of metrics from query log
clickhouse-local --multiquery --query "
clickhouse-local --query "
create view query_runs as select * from file('analyze/query-runs.tsv', TSV,
'test text, query_index int, query_id text, version UInt8, time float');
@ -582,7 +582,7 @@ numactl --cpunodebind=all --membind=all numactl --show
# If the available memory falls below 2 * size, GNU parallel will suspend some of the running jobs.
numactl --cpunodebind=all --membind=all parallel -v --joblog analyze/parallel-log.txt --memsuspend 15G --null < analyze/commands.txt 2>> analyze/errors.log
clickhouse-local --multiquery --query "
clickhouse-local --query "
-- Join the metric names back to the metric statistics we've calculated, and make
-- a denormalized table of them -- statistics for all metrics for all queries.
-- The WITH, ARRAY JOIN and CROSS JOIN do not like each other:
@ -680,7 +680,7 @@ rm ./*.{rep,svg} test-times.tsv test-dump.tsv unstable.tsv unstable-query-ids.ts
cat analyze/errors.log >> report/errors.log ||:
cat profile-errors.log >> report/errors.log ||:
clickhouse-local --multiquery --query "
clickhouse-local --query "
create view query_display_names as select * from
file('analyze/query-display-names.tsv', TSV,
'test text, query_index int, query_display_name text')
@ -981,7 +981,7 @@ create table all_query_metrics_tsv engine File(TSV, 'report/all-query-metrics.ts
for version in {right,left}
do
rm -rf data
clickhouse-local --multiquery --query "
clickhouse-local --query "
create view query_profiles as
with 0 as left, 1 as right
select * from file('analyze/query-profiles.tsv', TSV,
@ -1151,7 +1151,7 @@ function report_metrics
rm -rf metrics ||:
mkdir metrics
clickhouse-local --multiquery --query "
clickhouse-local --query "
create view right_async_metric_log as
select * from file('right-async-metric-log.tsv', TSVWithNamesAndTypes)
;
@ -1211,7 +1211,7 @@ function upload_results
# Prepare info for the CI checks table.
rm -f ci-checks.tsv
clickhouse-local --multiquery --query "
clickhouse-local --query "
create view queries as select * from file('report/queries.tsv', TSVWithNamesAndTypes);
create table ci_checks engine File(TSVWithNamesAndTypes, 'ci-checks.tsv')

View File

@ -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

View File

@ -1,3 +1,5 @@
-- Tags: no-parallel
DROP USER IF EXISTS test_user_01073;
DROP ROLE IF EXISTS test_role_01073;

View File

@ -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

View File

@ -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';

View File

@ -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"}']

View File

@ -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;

View File

@ -1 +1 @@
CREATE USER user IDENTIFIED WITH plaintext_password BY 'hello'
CREATE USER user IDENTIFIED WITH plaintext_password BY 'hello'

View File

@ -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

View File

@ -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

View File

@ -5,4 +5,4 @@ CUR_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
# shellcheck source=../shell_config.sh
. "$CUR_DIR"/../shell_config.sh
$CLICKHOUSE_CLIENT -n -q 'select 1 -- { clientError FOOBAR }' |& grep -o 'No error code with name:.*'
$CLICKHOUSE_CLIENT -q 'select 1 -- { clientError FOOBAR }' |& grep -o 'No error code with name:.*'

View File

@ -12,14 +12,14 @@ echo "
DROP TABLE IF EXISTS rocksdb_race;
CREATE TABLE rocksdb_race (key String, value UInt32) Engine=EmbeddedRocksDB PRIMARY KEY(key);
INSERT INTO rocksdb_race SELECT '1_' || toString(number), number FROM numbers(100000);
" | $CLICKHOUSE_CLIENT -n
" | $CLICKHOUSE_CLIENT
function read_stat_thread()
{
while true; do
echo "
SELECT * FROM system.rocksdb FORMAT Null;
" | $CLICKHOUSE_CLIENT -n
" | $CLICKHOUSE_CLIENT
done
}
@ -29,7 +29,7 @@ function truncate_thread()
sleep 3s;
echo "
TRUNCATE TABLE rocksdb_race;
" | $CLICKHOUSE_CLIENT -n
" | $CLICKHOUSE_CLIENT
done
}

View File

@ -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),

View File

@ -12,7 +12,7 @@ opts=(
--join_algorithm='parallel_hash'
)
$CLICKHOUSE_CLIENT -nq "
$CLICKHOUSE_CLIENT -q "
CREATE TABLE t1(a UInt32, b UInt32) ENGINE=MergeTree ORDER BY ();
INSERT INTO t1 SELECT number, number FROM numbers_mt(1e6);

Some files were not shown because too many files have changed in this diff Show More