Skip to content

Commit ecdfbcd

Browse files
committed
Support JWT in clickhouse-client (by upstream)
Fix fasttest Fix fasttest Fix Update src/Client/ConnectionPool.h Co-authored-by: Alexander Tokmakov <[email protected]> Update src/Storages/StorageReplicatedMergeTree.cpp Co-authored-by: Alexander Tokmakov <[email protected]> Forbid creating a user with a name equal to `JWT_AUTHENTICAION_MARKER` Cleanup Pass `connection_host` by ref Refactor with exceptions Lint Add new AuthenticationType to minimize conflicts Fix fastest Fix client failure Add docs
1 parent 97603bb commit ecdfbcd

File tree

23 files changed

+149
-175
lines changed

23 files changed

+149
-175
lines changed

docs/en/interfaces/cli.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,7 @@ You can pass parameters to `clickhouse-client` (all parameters have a default va
193193
- `--hardware-utilization` — Print hardware utilization information in progress bar.
194194
- `--print-profile-events` – Print `ProfileEvents` packets.
195195
- `--profile-events-delay-ms` – Delay between printing `ProfileEvents` packets (-1 - print only totals, 0 - print every single packet).
196+
- `--jwt` – If specified, enables authorization via JSON Web Token. Server JWT authorization is available only in ClickHouse Cloud.
196197

197198
Instead of `--host`, `--port`, `--user` and `--password` options, ClickHouse client also supports connection strings (see next section).
198199

docs/ru/interfaces/cli.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,7 @@ $ clickhouse-client --param_tbl="numbers" --param_db="system" --param_col="numbe
141141
- `--secure` — если указано, будет использован безопасный канал.
142142
- `--history_file` - путь к файлу с историей команд.
143143
- `--param_<name>` — значение параметра для [запроса с параметрами](#cli-queries-with-parameters).
144+
- `--jwt` – авторизация с использованием JSON Web Token. Доступно только в ClickHouse Cloud.
144145

145146
Вместо параметров `--host`, `--port`, `--user` и `--password` клиент ClickHouse также поддерживает строки подключения (смотри следующий раздел).
146147

programs/client/Client.cpp

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ namespace ErrorCodes
6363
extern const int NETWORK_ERROR;
6464
extern const int AUTHENTICATION_FAILED;
6565
extern const int NO_ELEMENTS_IN_CONFIG;
66+
extern const int USER_EXPIRED;
6667
}
6768

6869

@@ -73,6 +74,12 @@ void Client::processError(const String & query) const
7374
fmt::print(stderr, "Received exception from server (version {}):\n{}\n",
7475
server_version,
7576
getExceptionMessage(*server_exception, print_stack_trace, true));
77+
78+
if (server_exception->code() == ErrorCodes::USER_EXPIRED)
79+
{
80+
server_exception->rethrow();
81+
}
82+
7683
if (is_interactive)
7784
{
7885
fmt::print(stderr, "\n");
@@ -936,6 +943,7 @@ void Client::addOptions(OptionsDescription & options_description)
936943
("ssh-key-file", po::value<std::string>(), "File containing ssh private key needed for authentication. If not set does password authentication.")
937944
("ssh-key-passphrase", po::value<std::string>(), "Passphrase for imported ssh key.")
938945
("quota_key", po::value<std::string>(), "A string to differentiate quotas when the user have keyed quotas configured on server")
946+
("jwt", po::value<std::string>(), "Use JWT for authentication")
939947

940948
("max_client_network_bandwidth", po::value<int>(), "the maximum speed of data exchange over the network for the client in bytes per second.")
941949
("compression", po::value<bool>(), "enable or disable compression (enabled by default for remote communication and disabled for localhost communication).")
@@ -1093,6 +1101,12 @@ void Client::processOptions(const OptionsDescription & options_description,
10931101
config().setBool("no-warnings", true);
10941102
if (options.count("fake-drop"))
10951103
fake_drop = true;
1104+
if (options.count("jwt"))
1105+
{
1106+
if (!options["user"].defaulted())
1107+
throw Exception(ErrorCodes::BAD_ARGUMENTS, "User and JWT flags can't be specified together");
1108+
config().setString("jwt", options["jwt"].as<std::string>());
1109+
}
10961110
if (options.count("accept-invalid-certificate"))
10971111
{
10981112
config().setString("openSSL.client.invalidCertificateHandler.name", "AcceptCertificateHandler");

src/Access/Authentication.cpp

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,9 @@ bool Authentication::areCredentialsValid(
107107
case AuthenticationType::HTTP:
108108
throw Authentication::Require<BasicCredentials>("ClickHouse Basic Authentication");
109109

110+
case AuthenticationType::JWT:
111+
throw Exception(ErrorCodes::SUPPORT_IS_DISABLED, "JWT is available only in ClickHouse Cloud");
112+
110113
case AuthenticationType::KERBEROS:
111114
return external_authenticators.checkKerberosCredentials(auth_data.getKerberosRealm(), *gss_acceptor_context);
112115

@@ -144,6 +147,9 @@ bool Authentication::areCredentialsValid(
144147
case AuthenticationType::SSL_CERTIFICATE:
145148
throw Authentication::Require<BasicCredentials>("ClickHouse X.509 Authentication");
146149

150+
case AuthenticationType::JWT:
151+
throw Exception(ErrorCodes::SUPPORT_IS_DISABLED, "JWT is available only in ClickHouse Cloud");
152+
147153
case AuthenticationType::SSH_KEY:
148154
throw Authentication::Require<SshCredentials>("Ssh Keys Authentication");
149155

@@ -180,6 +186,9 @@ bool Authentication::areCredentialsValid(
180186
case AuthenticationType::SSH_KEY:
181187
throw Authentication::Require<SshCredentials>("Ssh Keys Authentication");
182188

189+
case AuthenticationType::JWT:
190+
throw Exception(ErrorCodes::SUPPORT_IS_DISABLED, "JWT is available only in ClickHouse Cloud");
191+
183192
case AuthenticationType::BCRYPT_PASSWORD:
184193
return checkPasswordBcrypt(basic_credentials->getPassword(), auth_data.getPasswordHashBinary());
185194

@@ -209,6 +218,9 @@ bool Authentication::areCredentialsValid(
209218
case AuthenticationType::HTTP:
210219
throw Authentication::Require<BasicCredentials>("ClickHouse Basic Authentication");
211220

221+
case AuthenticationType::JWT:
222+
throw Exception(ErrorCodes::SUPPORT_IS_DISABLED, "JWT is available only in ClickHouse Cloud");
223+
212224
case AuthenticationType::KERBEROS:
213225
throw Authentication::Require<GSSAcceptorContext>(auth_data.getKerberosRealm());
214226

@@ -236,6 +248,9 @@ bool Authentication::areCredentialsValid(
236248
case AuthenticationType::HTTP:
237249
throw Authentication::Require<BasicCredentials>("ClickHouse Basic Authentication");
238250

251+
case AuthenticationType::JWT:
252+
throw Exception(ErrorCodes::SUPPORT_IS_DISABLED, "JWT is available only in ClickHouse Cloud");
253+
239254
case AuthenticationType::KERBEROS:
240255
throw Authentication::Require<GSSAcceptorContext>(auth_data.getKerberosRealm());
241256

src/Access/AuthenticationData.cpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,7 @@ void AuthenticationData::setPassword(const String & password_)
126126
case AuthenticationType::BCRYPT_PASSWORD:
127127
case AuthenticationType::NO_PASSWORD:
128128
case AuthenticationType::LDAP:
129+
case AuthenticationType::JWT:
129130
case AuthenticationType::KERBEROS:
130131
case AuthenticationType::SSL_CERTIFICATE:
131132
case AuthenticationType::SSH_KEY:
@@ -231,6 +232,7 @@ void AuthenticationData::setPasswordHashBinary(const Digest & hash)
231232

232233
case AuthenticationType::NO_PASSWORD:
233234
case AuthenticationType::LDAP:
235+
case AuthenticationType::JWT:
234236
case AuthenticationType::KERBEROS:
235237
case AuthenticationType::SSL_CERTIFICATE:
236238
case AuthenticationType::SSH_KEY:
@@ -302,6 +304,10 @@ std::shared_ptr<ASTAuthenticationData> AuthenticationData::toAST() const
302304
node->children.push_back(std::make_shared<ASTLiteral>(getLDAPServerName()));
303305
break;
304306
}
307+
case AuthenticationType::JWT:
308+
{
309+
throw Exception(ErrorCodes::SUPPORT_IS_DISABLED, "JWT is available only in ClickHouse Cloud");
310+
}
305311
case AuthenticationType::KERBEROS:
306312
{
307313
const auto & realm = getKerberosRealm();

src/Access/Common/AuthenticationType.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,11 @@ const AuthenticationTypeInfo & AuthenticationTypeInfo::get(AuthenticationType ty
7272
static const auto info = make_info(Keyword::HTTP);
7373
return info;
7474
}
75+
case AuthenticationType::JWT:
76+
{
77+
static const auto info = make_info(Keyword::JWT);
78+
return info;
79+
}
7580
case AuthenticationType::MAX:
7681
break;
7782
}

src/Access/Common/AuthenticationType.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,9 @@ enum class AuthenticationType
4141
/// Authentication through HTTP protocol
4242
HTTP,
4343

44+
/// JSON Web Token
45+
JWT,
46+
4447
MAX,
4548
};
4649

src/Access/User.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@ void User::setName(const String & name_)
3333
throw Exception(ErrorCodes::BAD_ARGUMENTS, "User name '{}' is reserved", name_);
3434
if (startsWith(name_, EncodedUserInfo::SSH_KEY_AUTHENTICAION_MARKER))
3535
throw Exception(ErrorCodes::BAD_ARGUMENTS, "User name '{}' is reserved", name_);
36+
if (name_.starts_with(EncodedUserInfo::JWT_AUTHENTICAION_MARKER))
37+
throw Exception(ErrorCodes::BAD_ARGUMENTS, "User name '{}' is reserved", name_);
3638
name = name_;
3739
}
3840

src/Client/ClientBase.cpp

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,7 @@ namespace ErrorCodes
109109
extern const int USER_SESSION_LIMIT_EXCEEDED;
110110
extern const int NOT_IMPLEMENTED;
111111
extern const int CANNOT_READ_FROM_FILE_DESCRIPTOR;
112+
extern const int USER_EXPIRED;
112113
}
113114

114115
}
@@ -2243,7 +2244,7 @@ bool ClientBase::executeMultiQuery(const String & all_queries_text)
22432244
catch (...)
22442245
{
22452246
// Surprisingly, this is a client error. A server error would
2246-
// have been reported without throwing (see onReceiveSeverException()).
2247+
// have been reported without throwing (see onReceiveExceptionFromServer()).
22472248
client_exception = std::make_unique<Exception>(getCurrentExceptionMessageAndPattern(print_stack_trace), getCurrentExceptionCode());
22482249
have_error = true;
22492250
}
@@ -2616,6 +2617,9 @@ void ClientBase::runInteractive()
26162617
}
26172618
catch (const Exception & e)
26182619
{
2620+
if (e.code() == ErrorCodes::USER_EXPIRED)
2621+
break;
2622+
26192623
/// We don't need to handle the test hints in the interactive mode.
26202624
std::cerr << "Exception on client:" << std::endl << getExceptionMessage(e, print_stack_trace, true) << std::endl << std::endl;
26212625
client_exception.reset(e.clone());

src/Client/ClientBase.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,7 @@ class ClientBase : public Poco::Util::Application, public IHints<2>
129129
const std::vector<Arguments> & hosts_and_ports_arguments) = 0;
130130
virtual void processConfig() = 0;
131131

132+
/// Returns true if query processing was successful.
132133
bool processQueryText(const String & text);
133134

134135
virtual void readArguments(

0 commit comments

Comments
 (0)