Skip to content

Commit f107d8e

Browse files
vitlibarmkmkme
authored andcommitted
Merge pull request ClickHouse#70775 from shiyer7474/impersonate_user
Add feature to impersonate/proxy user sessions
1 parent f36ec92 commit f107d8e

25 files changed

+518
-2
lines changed

ci/jobs/scripts/check_style/aspell-ignore/en/aspell-dict.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1355,6 +1355,7 @@ atanh
13551355
atomicity
13561356
auth
13571357
authType
1358+
authenticatedUser
13581359
authenticator
13591360
authenticators
13601361
autocompletion
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
---
2+
description: 'Documentation for EXECUTE AS Statement'
3+
sidebar_label: 'EXECUTE AS'
4+
sidebar_position: 53
5+
slug: /sql-reference/statements/execute_as
6+
title: 'EXECUTE AS Statement'
7+
doc_type: 'reference'
8+
---
9+
10+
# EXECUTE AS Statement
11+
12+
Allows to execute queries on behalf of a different user.
13+
14+
## Syntax {#syntax}
15+
16+
```sql
17+
EXECUTE AS target_user;
18+
EXECUTE AS target_user subquery;
19+
```
20+
21+
The first form (without `subquery`) sets that all the following queries in the current session will be executed on behalf of the specified `target_user`.
22+
23+
The second form (with `subquery`) executes only the specified `subquery` on behalf of the specified `target_user`.
24+
25+
In order to work both forms require server setting [allow_impersonate_user](/operations/server-configuration-parameters/settings#allow_impersonate_user)
26+
to be set to `1` and the `IMPERSONATE` privilege to be granted. For example, the following commands
27+
```sql
28+
GRANT IMPERSONATE ON user1 TO user2;
29+
GRANT IMPERSONATE ON * TO user3;
30+
```
31+
allow user `user2` to execute commands `EXECUTE AS user1 ...` and also allow user `user3` to execute commands as any user.
32+
33+
While impersonating another user function [currentUser()](/sql-reference/functions/other-functions#currentUser) returns the name of that other user,
34+
and function [authenticatedUser()](/sql-reference/functions/other-functions#authenticatedUser) returns the name of the user who has been actually authenticated.
35+
36+
## Examples {#examples}
37+
38+
```sql
39+
SELECT currentUser(), authenticatedUser(); -- outputs "default default"
40+
CREATE USER james;
41+
EXECUTE AS james SELECT currentUser(), authenticatedUser(); -- outputs "james default"
42+
```

src/Access/Common/AccessType.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,7 @@ enum class AccessType : uint8_t
154154
M(SHOW_QUOTAS, "SHOW CREATE QUOTA", GLOBAL, SHOW_ACCESS) \
155155
M(SHOW_SETTINGS_PROFILES, "SHOW PROFILES, SHOW CREATE SETTINGS PROFILE, SHOW CREATE PROFILE", GLOBAL, SHOW_ACCESS) \
156156
M(SHOW_ACCESS, "", GROUP, ACCESS_MANAGEMENT) \
157+
M(IMPERSONATE, "EXECUTE AS", USER_NAME, ACCESS_MANAGEMENT) \
157158
M(ACCESS_MANAGEMENT, "", GROUP, ALL) \
158159
M(SHOW_NAMED_COLLECTIONS, "SHOW NAMED COLLECTIONS", NAMED_COLLECTION, NAMED_COLLECTION_ADMIN) \
159160
M(SHOW_NAMED_COLLECTIONS_SECRETS, "SHOW NAMED COLLECTIONS SECRETS", NAMED_COLLECTION, NAMED_COLLECTION_ADMIN) \

src/Core/ServerSettings.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1019,8 +1019,8 @@ namespace DB
10191019
<wait_dictionaries_load_at_startup>true</wait_dictionaries_load_at_startup>
10201020
```
10211021
)", 0) \
1022-
DECLARE(Bool, storage_shared_set_join_use_inner_uuid, false, "If enabled, an inner UUID is generated during the creation of SharedSet and SharedJoin. ClickHouse Cloud only", 0)
1023-
1022+
DECLARE(Bool, storage_shared_set_join_use_inner_uuid, false, "If enabled, an inner UUID is generated during the creation of SharedSet and SharedJoin. ClickHouse Cloud only", 0) \
1023+
DECLARE(Bool, allow_impersonate_user, false, R"(Enable/disable the IMPERSONATE feature (EXECUTE AS target_user).)", 0) \
10241024

10251025
// clang-format on
10261026

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
#include <Functions/IFunction.h>
2+
#include <Functions/FunctionFactory.h>
3+
#include <Interpreters/Context.h>
4+
#include <DataTypes/DataTypeString.h>
5+
#include <Core/Field.h>
6+
7+
8+
namespace DB
9+
{
10+
namespace
11+
{
12+
13+
class FunctionAuthenticatedUser : public IFunction
14+
{
15+
const String user_name;
16+
17+
public:
18+
static constexpr auto name = "authenticatedUser";
19+
static FunctionPtr create(ContextPtr context)
20+
{
21+
return std::make_shared<FunctionAuthenticatedUser>(context->getClientInfo().authenticated_user);
22+
}
23+
24+
explicit FunctionAuthenticatedUser(const String & user_name_) : user_name{user_name_}
25+
{
26+
}
27+
28+
String getName() const override
29+
{
30+
return name;
31+
}
32+
size_t getNumberOfArguments() const override
33+
{
34+
return 0;
35+
}
36+
37+
DataTypePtr getReturnTypeImpl(const DataTypes & /*arguments*/) const override
38+
{
39+
return std::make_shared<DataTypeString>();
40+
}
41+
42+
bool isDeterministic() const override { return false; }
43+
44+
bool isSuitableForShortCircuitArgumentsExecution(const DataTypesWithConstInfo & /*arguments*/) const override { return false; }
45+
46+
ColumnPtr executeImpl(const ColumnsWithTypeAndName &, const DataTypePtr &, size_t input_rows_count) const override
47+
{
48+
return DataTypeString().createColumnConst(input_rows_count, user_name);
49+
}
50+
};
51+
52+
}
53+
54+
REGISTER_FUNCTION(AuthenticatedUser)
55+
{
56+
factory.registerFunction<FunctionAuthenticatedUser>(FunctionDocumentation{
57+
.description=R"(
58+
If the session user has been switched using the EXECUTE AS command, this function returns the name of the original user that was used for authentication and creating the session.
59+
Alias: authUser()
60+
)",
61+
.syntax=R"(authenticatedUser())",
62+
.arguments={},
63+
.returned_value="String - The name of the authenticated user.",
64+
.examples{
65+
{"Usage example",
66+
R"(
67+
EXECUTE as u1;
68+
SELECT currentUser(), authenticatedUser();
69+
)",
70+
R"(
71+
┌─currentUser()─┬─authenticatedUser()─┐
72+
│ u1 │ default │
73+
└───────────────┴─────────────────────┘
74+
)"
75+
}},
76+
.category = {"Other"}
77+
});
78+
79+
factory.registerAlias("authUser", "authenticatedUser");
80+
}
81+
82+
}
Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
#include <Interpreters/Access/InterpreterExecuteAsQuery.h>
2+
3+
#include <Access/AccessControl.h>
4+
#include <Access/User.h>
5+
#include <Core/Settings.h>
6+
#include <Core/ServerSettings.h>
7+
#include <Parsers/Access/ASTExecuteAsQuery.h>
8+
#include <Parsers/Access/ASTUserNameWithHost.h>
9+
#include <Interpreters/Context.h>
10+
#include <Interpreters/InterpreterFactory.h>
11+
#include <Interpreters/QueryFlags.h>
12+
#include <Interpreters/executeQuery.h>
13+
14+
15+
namespace DB
16+
{
17+
18+
namespace ErrorCodes
19+
{
20+
extern const int SUPPORT_IS_DISABLED;
21+
}
22+
23+
namespace ServerSetting
24+
{
25+
extern const ServerSettingsBool allow_impersonate_user;
26+
}
27+
28+
namespace
29+
{
30+
/// Creates another query context to execute a query as another user.
31+
ContextMutablePtr impersonateQueryContext(ContextPtr context, const String & target_user_name)
32+
{
33+
auto new_context = Context::createCopy(context->getGlobalContext());
34+
new_context->setClientInfo(context->getClientInfo());
35+
new_context->makeQueryContext();
36+
37+
const auto & database = context->getCurrentDatabase();
38+
if (!database.empty() && database != new_context->getCurrentDatabase())
39+
new_context->setCurrentDatabase(database);
40+
41+
new_context->setInsertionTable(context->getInsertionTable(), context->getInsertionTableColumnNames());
42+
new_context->setProgressCallback(context->getProgressCallback());
43+
new_context->setProcessListElement(context->getProcessListElement());
44+
45+
if (context->getCurrentTransaction())
46+
new_context->setCurrentTransaction(context->getCurrentTransaction());
47+
48+
if (context->getZooKeeperMetadataTransaction())
49+
new_context->initZooKeeperMetadataTransaction(context->getZooKeeperMetadataTransaction());
50+
51+
new_context->setUser(context->getAccessControl().getID<User>(target_user_name));
52+
53+
/// We need to update the client info to make currentUser() return `target_user_name`.
54+
new_context->setCurrentUserName(target_user_name);
55+
new_context->setInitialUserName(target_user_name);
56+
57+
auto changed_settings = context->getSettingsRef().changes();
58+
new_context->clampToSettingsConstraints(changed_settings, SettingSource::QUERY);
59+
new_context->applySettingsChanges(changed_settings);
60+
61+
return new_context;
62+
}
63+
64+
/// Changes the session context to execute all following queries in this session as another user.
65+
void impersonateSessionContext(ContextMutablePtr context, const String & target_user_name)
66+
{
67+
auto database = context->getCurrentDatabase();
68+
auto changed_settings = context->getSettingsRef().changes();
69+
70+
context->setUser(context->getAccessControl().getID<User>(target_user_name));
71+
72+
/// We need to update the client info to make currentUser() return `target_user_name`.
73+
context->setCurrentUserName(target_user_name);
74+
context->setInitialUserName(target_user_name);
75+
76+
context->clampToSettingsConstraints(changed_settings, SettingSource::QUERY);
77+
context->applySettingsChanges(changed_settings);
78+
79+
if (!database.empty() && database != context->getCurrentDatabase())
80+
context->setCurrentDatabase(database);
81+
}
82+
}
83+
84+
85+
BlockIO InterpreterExecuteAsQuery::execute()
86+
{
87+
if (!getContext()->getGlobalContext()->getServerSettings()[ServerSetting::allow_impersonate_user])
88+
throw Exception(ErrorCodes::SUPPORT_IS_DISABLED, "IMPERSONATE feature is disabled, set allow_impersonate_user to 1 to enable");
89+
90+
const auto & query = query_ptr->as<const ASTExecuteAsQuery &>();
91+
String target_user_name = query.target_user->toString();
92+
getContext()->checkAccess(AccessType::IMPERSONATE, target_user_name);
93+
94+
if (query.subquery)
95+
{
96+
/// EXECUTE AS <user> <subquery>
97+
auto subquery_context = impersonateQueryContext(getContext(), target_user_name);
98+
return executeQuery(query.subquery->formatWithSecretsOneLine(), subquery_context, QueryFlags{ .internal = true }).second;
99+
}
100+
else
101+
{
102+
/// EXECUTE AS <user>
103+
impersonateSessionContext(getContext()->getSessionContext(), target_user_name);
104+
return {};
105+
}
106+
}
107+
108+
109+
void registerInterpreterExecuteAsQuery(InterpreterFactory & factory)
110+
{
111+
auto create_fn = [] (const InterpreterFactory::Arguments & args)
112+
{
113+
return std::make_unique<InterpreterExecuteAsQuery>(args.query, args.context);
114+
};
115+
factory.registerInterpreter("InterpreterExecuteAsQuery", create_fn);
116+
}
117+
118+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
#pragma once
2+
3+
#include <Interpreters/IInterpreter.h>
4+
#include <Parsers/IAST_fwd.h>
5+
6+
7+
namespace DB
8+
{
9+
10+
class InterpreterExecuteAsQuery : public IInterpreter, WithMutableContext
11+
{
12+
public:
13+
InterpreterExecuteAsQuery(const ASTPtr & query_ptr_, ContextMutablePtr context_) : WithMutableContext(context_), query_ptr(query_ptr_) {}
14+
BlockIO execute() override;
15+
16+
private:
17+
ASTPtr query_ptr;
18+
};
19+
20+
}

src/Interpreters/ClientInfo.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,9 @@ class ClientInfo
6363
String current_query_id;
6464
std::shared_ptr<Poco::Net::SocketAddress> current_address;
6565

66+
/// For IMPERSONATEd session, stores the original authenticated user
67+
String authenticated_user;
68+
6669
/// When query_kind == INITIAL_QUERY, these values are equal to current.
6770
String initial_user;
6871
String initial_query_id;

src/Interpreters/InterpreterFactory.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@
5353
#include <Parsers/Access/ASTShowCreateAccessEntityQuery.h>
5454
#include <Parsers/Access/ASTShowGrantsQuery.h>
5555
#include <Parsers/Access/ASTShowPrivilegesQuery.h>
56+
#include <Parsers/Access/ASTExecuteAsQuery.h>
5657
#include <Parsers/ASTDescribeCacheQuery.h>
5758

5859
#include <Interpreters/InterpreterFactory.h>
@@ -382,6 +383,10 @@ InterpreterFactory::InterpreterPtr InterpreterFactory::get(ASTPtr & query, Conte
382383
{
383384
interpreter_name = "InterpreterParallelWithQuery";
384385
}
386+
else if (query->as<ASTExecuteAsQuery>())
387+
{
388+
interpreter_name = "InterpreterExecuteAsQuery";
389+
}
385390

386391
if (!interpreters.contains(interpreter_name))
387392
throw Exception(ErrorCodes::UNKNOWN_TYPE_OF_QUERY, "Unknown type of query: {}", query->getID());

src/Interpreters/QueryLog.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,7 @@ ColumnsDescription QueryLogElement::getColumnsDescription()
103103
{"initial_port", std::make_shared<DataTypeUInt16>(), "The client port that was used to make the parent query."},
104104
{"initial_query_start_time", std::make_shared<DataTypeDateTime>(), "Initial query starting time (for distributed query execution)."},
105105
{"initial_query_start_time_microseconds", std::make_shared<DataTypeDateTime64>(6), "Initial query starting time with microseconds precision (for distributed query execution)."},
106+
{"authenticated_user", low_cardinality_string, "Name of the user who was authenticated in the session."},
106107
{"interface", std::make_shared<DataTypeUInt8>(), "Interface that the query was initiated from. Possible values: 1 — TCP, 2 — HTTP."},
107108
{"is_secure", std::make_shared<DataTypeUInt8>(), "The flag whether a query was executed over a secure interface"},
108109
{"os_user", low_cardinality_string, "Operating system username who runs clickhouse-client."},
@@ -331,6 +332,8 @@ void QueryLogElement::appendClientInfo(const ClientInfo & client_info, MutableCo
331332
columns[i++]->insert(client_info.initial_query_start_time);
332333
columns[i++]->insert(client_info.initial_query_start_time_microseconds);
333334

335+
columns[i++]->insertData(client_info.authenticated_user.data(), client_info.authenticated_user.size());
336+
334337
columns[i++]->insert(static_cast<UInt64>(client_info.interface));
335338
columns[i++]->insert(static_cast<UInt64>(client_info.is_secure));
336339

0 commit comments

Comments
 (0)