Compare commits
183 Commits
next-2.2
...
branch-2.1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
64f1aa8d99 | ||
|
|
280e6eedb9 | ||
|
|
f80f15a6af | ||
|
|
d0eb0c0b90 | ||
|
|
1427c4d428 | ||
|
|
034f2cb42d | ||
|
|
e043a5c276 | ||
|
|
5da9bd3a6e | ||
|
|
3578027e2e | ||
|
|
7d2150a057 | ||
|
|
afd3c571cc | ||
|
|
093c8512db | ||
|
|
9c0b8ec736 | ||
|
|
1794b732b0 | ||
|
|
c1ac4fb8b0 | ||
|
|
2e7e59fb50 | ||
|
|
af29d4bed3 | ||
|
|
72494bbe05 | ||
|
|
5784823888 | ||
|
|
a7633be1a9 | ||
|
|
e78ded74ce | ||
|
|
6615c2a6a9 | ||
|
|
11500ccd3a | ||
|
|
955f3eeb56 | ||
|
|
08bfd96774 | ||
|
|
f6c4d558eb | ||
|
|
0040ff6de2 | ||
|
|
c238bc7a81 | ||
|
|
3b984a4293 | ||
|
|
156761d77e | ||
|
|
8e33e80ad3 | ||
|
|
c35dd86c87 | ||
|
|
87cb8a1fa4 | ||
|
|
26f3340c32 | ||
|
|
aaba093371 | ||
|
|
a64c6e6be9 | ||
|
|
c83d2d0d77 | ||
|
|
0aa49d0311 | ||
|
|
cce455b1f5 | ||
|
|
6772f3806b | ||
|
|
6c9d699835 | ||
|
|
a75e1632c8 | ||
|
|
c5718bf620 | ||
|
|
2315fcd6cf | ||
|
|
8c5464d2fd | ||
|
|
346d2788e3 | ||
|
|
4f68fede6d | ||
|
|
681f9e4f50 | ||
|
|
c503bc7693 | ||
|
|
de7024251b | ||
|
|
9a0eb2319c | ||
|
|
9ef462449b | ||
|
|
6271f30716 | ||
|
|
8b64e80c88 | ||
|
|
c5bffcaa68 | ||
|
|
8aa0b60e91 | ||
|
|
dccf762654 | ||
|
|
e5344079d9 | ||
|
|
7bc8515c48 | ||
|
|
1228a41eaa | ||
|
|
58b90ceee0 | ||
|
|
ef46067606 | ||
|
|
ffdd0f6392 | ||
|
|
3ab1c8abff | ||
|
|
d306c40507 | ||
|
|
b98d5b30de | ||
|
|
85f5e57502 | ||
|
|
19158f3401 | ||
|
|
a7e40d6acb | ||
|
|
eedcfedd5a | ||
|
|
b655fe262b | ||
|
|
cbb3b959e3 | ||
|
|
3dd282f7f0 | ||
|
|
574548e50f | ||
|
|
688d58f54a | ||
|
|
ea9b0bb4b0 | ||
|
|
6a9b026601 | ||
|
|
adc1523aaa | ||
|
|
5444eead08 | ||
|
|
1e74362ec9 | ||
|
|
72e52dafba | ||
|
|
29746e1e7b | ||
|
|
13cd56774f | ||
|
|
812018479b | ||
|
|
0ee2462811 | ||
|
|
c8bc3a7053 | ||
|
|
9f78799e80 | ||
|
|
5bba3856ca | ||
|
|
63e92418dd | ||
|
|
9eaa6f233e | ||
|
|
6600317b2c | ||
|
|
807acb2dd9 | ||
|
|
5e44bf97f0 | ||
|
|
4003be40b3 | ||
|
|
cf059b6ee2 | ||
|
|
d96c31ee4d | ||
|
|
680ce234b0 | ||
|
|
ad656b2c55 | ||
|
|
43101b6bff | ||
|
|
492a5c8886 | ||
|
|
152747b8fd | ||
|
|
00c08519a7 | ||
|
|
5d47a39b7b | ||
|
|
4f8e8bdc04 | ||
|
|
ef1dab4565 | ||
|
|
3f602814ba | ||
|
|
83d4e85e00 | ||
|
|
857ffeefce | ||
|
|
a845e23702 | ||
|
|
f9b14df3a3 | ||
|
|
ae47dfde7d | ||
|
|
cc15a13365 | ||
|
|
6e14dcb84c | ||
|
|
9ed64cc11c | ||
|
|
d4c46afc50 | ||
|
|
f371d17884 | ||
|
|
0a82a885a4 | ||
|
|
17febfdb0e | ||
|
|
830bf99528 | ||
|
|
90000d9861 | ||
|
|
46dae42dcd | ||
|
|
d6395634ad | ||
|
|
d886b3def4 | ||
|
|
bcb06bb043 | ||
|
|
4606300b25 | ||
|
|
282d93de99 | ||
|
|
52d3403cb0 | ||
|
|
97f6073699 | ||
|
|
5454e6e168 | ||
|
|
498fb11c70 | ||
|
|
a6b4881994 | ||
|
|
9848df6667 | ||
|
|
2090a5f8f6 | ||
|
|
7634ed39eb | ||
|
|
fb9b15904a | ||
|
|
4e11f05aa7 | ||
|
|
516a1ae834 | ||
|
|
be5127388d | ||
|
|
6d0679ca72 | ||
|
|
eb67b427b2 | ||
|
|
2931324b34 | ||
|
|
614519c4be | ||
|
|
203b924c76 | ||
|
|
f4f957fa53 | ||
|
|
39e614a444 | ||
|
|
d8521d0fa2 | ||
|
|
f60696b55f | ||
|
|
1b15a0926a | ||
|
|
32efd3902c | ||
|
|
6b2f7f8c39 | ||
|
|
370a6482e3 | ||
|
|
981644167b | ||
|
|
6f669da227 | ||
|
|
bdf1173075 | ||
|
|
106c69ad45 | ||
|
|
740fcc73b8 | ||
|
|
cefbb0b999 | ||
|
|
02f43f5e4c | ||
|
|
8850ef7c59 | ||
|
|
8567723a7b | ||
|
|
b0b7c73acd | ||
|
|
eb82d66849 | ||
|
|
eb12fb3733 | ||
|
|
60d011c9c0 | ||
|
|
7c3390bde8 | ||
|
|
95b55a0e9d | ||
|
|
7785d8f396 | ||
|
|
b805e37d30 | ||
|
|
a790b8cd20 | ||
|
|
a10ea80a63 | ||
|
|
91a5c9d20c | ||
|
|
f846b897bf | ||
|
|
8d7c34bf68 | ||
|
|
7449586a26 | ||
|
|
b601b9f078 | ||
|
|
1ec81cda37 | ||
|
|
e87a2bc9c0 | ||
|
|
b84d13d325 | ||
|
|
b5abf6541d | ||
|
|
8cf869cb37 | ||
|
|
df509761b0 | ||
|
|
b90e11264e | ||
|
|
84b2bff0a6 |
3
.gitmodules
vendored
3
.gitmodules
vendored
@@ -9,6 +9,3 @@
|
||||
[submodule "dist/ami/files/scylla-ami"]
|
||||
path = dist/ami/files/scylla-ami
|
||||
url = ../scylla-ami
|
||||
[submodule "xxHash"]
|
||||
path = xxHash
|
||||
url = ../xxHash
|
||||
|
||||
@@ -125,7 +125,7 @@ list(REMOVE_ITEM SEASTAR_CFLAGS "-DHAVE_GCC6_CONCEPTS")
|
||||
#
|
||||
# For ease of browsing the source code, we always pretend that DPDK is enabled.
|
||||
target_compile_options(scylla PUBLIC
|
||||
-std=gnu++1z
|
||||
-std=gnu++14
|
||||
-DHAVE_DPDK
|
||||
-DHAVE_HWLOC
|
||||
"${SEASTAR_CFLAGS}")
|
||||
@@ -137,5 +137,4 @@ target_include_directories(scylla PUBLIC
|
||||
${SEASTAR_DPDK_INCLUDE_DIRS}
|
||||
${SEASTAR_INCLUDE_DIRS}
|
||||
${Boost_INCLUDE_DIRS}
|
||||
xxhash
|
||||
build/release/gen)
|
||||
|
||||
48
HACKING.md
48
HACKING.md
@@ -85,53 +85,7 @@ The `-c1 -m1G` arguments limit this Seastar-based test to a single system thread
|
||||
|
||||
All changes to Scylla are submitted as patches to the public mailing list. Once a patch is approved by one of the maintainers of the project, it is committed to the maintainers' copy of the repository at https://github.com/scylladb/scylla.
|
||||
|
||||
Detailed instructions for formatting patches for the mailing list and advice on preparing good patches are available at the [ScyllaDB website](http://docs.scylladb.com/contribute/). There are also some guidelines that can help you make the patch review process smoother:
|
||||
|
||||
1. Before generating patches, make sure your Git configuration points to `.gitorderfile`. You can do it by running
|
||||
|
||||
```bash
|
||||
$ git config diff.orderfile .gitorderfile
|
||||
```
|
||||
|
||||
2. If you are sending more than a single patch, push your changes into a new branch of your fork of Scylla on GitHub and add a URL pointing to this branch to your cover letter.
|
||||
|
||||
3. If you are sending a new revision of an earlier patchset, add a brief summary of changes in this version, for example:
|
||||
```
|
||||
In v3:
|
||||
- declared move constructor and move assignment operator as noexcept
|
||||
- used std::variant instead of a union
|
||||
...
|
||||
```
|
||||
|
||||
4. Add information about the tests run with this fix. It can look like
|
||||
```
|
||||
"Tests: unit ({mode}), dtest ({smp})"
|
||||
```
|
||||
|
||||
The usual is "Tests: unit (release)", although running debug tests is encouraged.
|
||||
|
||||
5. When answering review comments, prefer inline quotes as they make it easier to track the conversation across multiple e-mails.
|
||||
|
||||
### Finding a person to review and merge your patches
|
||||
|
||||
You can use the `scripts/find-maintainer` script to find a subsystem maintainer and/or reviewer for your patches. The script accepts a filename in the git source tree as an argument and outputs a list of subsystems the file belongs to and their respective maintainers and reviewers. For example, if you changed the `cql3/statements/create_view_statement.hh` file, run the script as follows:
|
||||
|
||||
```bash
|
||||
$ ./scripts/find-maintainer cql3/statements/create_view_statement.hh
|
||||
```
|
||||
|
||||
and you will get output like this:
|
||||
|
||||
```
|
||||
CQL QUERY LANGUAGE
|
||||
Tomasz Grabiec <tgrabiec@scylladb.com> [maintainer]
|
||||
Pekka Enberg <penberg@scylladb.com> [maintainer]
|
||||
MATERIALIZED VIEWS
|
||||
Pekka Enberg <penberg@scylladb.com> [maintainer]
|
||||
Duarte Nunes <duarte@scylladb.com> [maintainer]
|
||||
Nadav Har'El <nyh@scylladb.com> [reviewer]
|
||||
Duarte Nunes <duarte@scylladb.com> [reviewer]
|
||||
```
|
||||
Detailed instructions for formatting patches for the mailing list and advice on preparing good patches are available at the [ScyllaDB website](http://docs.scylladb.com/contribute/).
|
||||
|
||||
### Running Scylla
|
||||
|
||||
|
||||
131
MAINTAINERS
131
MAINTAINERS
@@ -1,131 +0,0 @@
|
||||
M: Maintainer with commit access
|
||||
R: Reviewer with subsystem expertise
|
||||
F: Filename, directory, or pattern for the subsystem
|
||||
|
||||
---
|
||||
|
||||
AUTH
|
||||
M: Paweł Dziepak <pdziepak@scylladb.com>
|
||||
M: Duarte Nunes <duarte@scylladb.com>
|
||||
R: Calle Wilund <calle@scylladb.com>
|
||||
R: Vlad Zolotarov <vladz@scylladb.com>
|
||||
R: Jesse Haber-Kucharsky <jhaberku@scylladb.com>
|
||||
F: auth/*
|
||||
|
||||
CACHE
|
||||
M: Tomasz Grabiec <tgrabiec@scylladb.com>
|
||||
M: Paweł Dziepak <pdziepak@scylladb.com>
|
||||
R: Piotr Jastrzebski <piotr@scylladb.com>
|
||||
F: row_cache*
|
||||
F: *mutation*
|
||||
F: tests/mvcc*
|
||||
|
||||
COMMITLOG / BATCHLOGa
|
||||
M: Paweł Dziepak <pdziepak@scylladb.com>
|
||||
M: Duarte Nunes <duarte@scylladb.com>
|
||||
R: Calle Wilund <calle@scylladb.com>
|
||||
F: db/commitlog/*
|
||||
F: db/batch*
|
||||
|
||||
COORDINATOR
|
||||
M: Paweł Dziepak <pdziepak@scylladb.com>
|
||||
M: Duarte Nunes <duarte@scylladb.com>
|
||||
R: Gleb Natapov <gleb@scylladb.com>
|
||||
F: service/storage_proxy*
|
||||
|
||||
COMPACTION
|
||||
R: Raphael S. Carvalho <raphaelsc@scylladb.com>
|
||||
R: Glauber Costa <glauber@scylladb.com>
|
||||
R: Nadav Har'El <nyh@scylladb.com>
|
||||
F: sstables/compaction*
|
||||
|
||||
CQL TRANSPORT LAYER
|
||||
M: Pekka Enberg <penberg@scylladb.com>
|
||||
F: transport/*
|
||||
|
||||
CQL QUERY LANGUAGE
|
||||
M: Tomasz Grabiec <tgrabiec@scylladb.com>
|
||||
M: Pekka Enberg <penberg@scylladb.com>
|
||||
F: cql3/*
|
||||
|
||||
COUNTERS
|
||||
M: Paweł Dziepak <pdziepak@scylladb.com>
|
||||
F: counters*
|
||||
F: tests/counter_test*
|
||||
|
||||
GOSSIP
|
||||
M: Duarte Nunes <duarte@scylladb.com>
|
||||
M: Tomasz Grabiec <tgrabiec@scylladb.com>
|
||||
R: Asias He <asias@scylladb.com>
|
||||
F: gms/*
|
||||
|
||||
DOCKER
|
||||
M: Pekka Enberg <penberg@scylladb.com>
|
||||
F: dist/docker/*
|
||||
|
||||
LSA
|
||||
M: Tomasz Grabiec <tgrabiec@scylladb.com>
|
||||
M: Paweł Dziepak <pdziepak@scylladb.com>
|
||||
F: utils/logalloc*
|
||||
|
||||
MATERIALIZED VIEWS
|
||||
M: Duarte Nunes <duarte@scylladb.com>
|
||||
M: Pekka Enberg <penberg@scylladb.com>
|
||||
R: Nadav Har'El <nyh@scylladb.com>
|
||||
R: Duarte Nunes <duarte@scylladb.com>
|
||||
F: db/view/*
|
||||
F: cql3/statements/*view*
|
||||
|
||||
PACKAGING
|
||||
R: Takuya ASADA <syuu@scylladb.com>
|
||||
F: dist/*
|
||||
|
||||
REPAIR
|
||||
M: Tomasz Grabiec <tgrabiec@scylladb.com>
|
||||
M: Duarte Nunes <duarte@scylladb.com>
|
||||
R: Asias He <asias@scylladb.com>
|
||||
R: Nadav Har'El <nyh@scylladb.com>
|
||||
F: repair/*
|
||||
|
||||
SCHEMA MANAGEMENT
|
||||
M: Tomasz Grabiec <tgrabiec@scylladb.com>
|
||||
M: Duarte Nunes <duarte@scylladb.com>
|
||||
M: Pekka Enberg <penberg@scylladb.com>
|
||||
F: db/schema_tables*
|
||||
F: db/legacy_schema_migrator*
|
||||
F: service/migration*
|
||||
F: schema*
|
||||
|
||||
SECONDARY INDEXES
|
||||
M: Pekka Enberg <penberg@scylladb.com>
|
||||
M: Duarte Nunes <duarte@scylladb.com>
|
||||
R: Nadav Har'El <nyh@scylladb.com>
|
||||
R: Pekka Enberg <penberg@scylladb.com>
|
||||
F: db/index/*
|
||||
F: cql3/statements/*index*
|
||||
|
||||
SSTABLES
|
||||
M: Tomasz Grabiec <tgrabiec@scylladb.com>
|
||||
M: Duarte Nunes <duarte@scylladb.com>
|
||||
R: Raphael S. Carvalho <raphaelsc@scylladb.com>
|
||||
R: Glauber Costa <glauber@scylladb.com>
|
||||
R: Nadav Har'El <nyh@scylladb.com>
|
||||
F: sstables/*
|
||||
|
||||
STREAMING
|
||||
M: Tomasz Grabiec <tgrabiec@scylladb.com>
|
||||
M: Duarte Nunes <duarte@scylladb.com>
|
||||
R: Asias He <asias@scylladb.com>
|
||||
F: streaming/*
|
||||
F: service/storage_service.*
|
||||
|
||||
THRIFT TRANSPORT LAYER
|
||||
M: Duarte Nunes <duarte@scylladb.com>
|
||||
F: thrift/*
|
||||
|
||||
THE REST
|
||||
M: Avi Kivity <avi@scylladb.com>
|
||||
M: Paweł Dziepak <pdziepak@scylladb.com>
|
||||
M: Duarte Nunes <duarte@scylladb.com>
|
||||
M: Tomasz Grabiec <tgrabiec@scylladb.com>
|
||||
F: *
|
||||
@@ -1,5 +1,2 @@
|
||||
This project includes code developed by the Apache Software Foundation (http://www.apache.org/),
|
||||
especially Apache Cassandra.
|
||||
|
||||
It also includes files from https://github.com/antonblanchard/crc32-vpmsum (author Anton Blanchard <anton@au.ibm.com>, IBM).
|
||||
These files are located in utils/arch/powerpc/crc32-vpmsum. Their license may be found in licenses/LICENSE-crc32-vpmsum.TXT.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#!/bin/sh
|
||||
|
||||
VERSION=2.2.2
|
||||
VERSION=2.1.6
|
||||
|
||||
if test -f version
|
||||
then
|
||||
|
||||
@@ -792,24 +792,6 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"path":"/storage_service/active_repair/",
|
||||
"operations":[
|
||||
{
|
||||
"method":"GET",
|
||||
"summary":"Return an array with the ids of the currently active repairs",
|
||||
"type":"array",
|
||||
"items":{
|
||||
"type":"int"
|
||||
},
|
||||
"nickname":"get_active_repair_async",
|
||||
"produces":[
|
||||
"application/json"
|
||||
],
|
||||
"parameters":[]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"path":"/storage_service/repair_async/{keyspace}",
|
||||
"operations":[
|
||||
@@ -2193,11 +2175,11 @@
|
||||
"description":"The column family"
|
||||
},
|
||||
"total":{
|
||||
"type":"long",
|
||||
"type":"int",
|
||||
"description":"The total snapshot size"
|
||||
},
|
||||
"live":{
|
||||
"type":"long",
|
||||
"type":"int",
|
||||
"description":"The live snapshot size"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,29 +0,0 @@
|
||||
{
|
||||
"swagger": "2.0",
|
||||
"info": {
|
||||
"version": "1.0.0",
|
||||
"title": "Scylla API",
|
||||
"description": "The scylla API version 2.0",
|
||||
"termsOfService": "http://www.scylladb.com/tos/",
|
||||
"contact": {
|
||||
"name": "Scylla Team",
|
||||
"email": "info@scylladb.com",
|
||||
"url": "http://scylladb.com"
|
||||
},
|
||||
"license": {
|
||||
"name": "AGPL",
|
||||
"url": "https://github.com/scylladb/scylla/blob/master/LICENSE.AGPL"
|
||||
}
|
||||
},
|
||||
"host": "{{Host}}",
|
||||
"basePath": "/v2",
|
||||
"schemes": [
|
||||
"http"
|
||||
],
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"paths": {
|
||||
13
api/api.cc
13
api/api.cc
@@ -54,17 +54,14 @@ static std::unique_ptr<reply> exception_reply(std::exception_ptr eptr) {
|
||||
|
||||
future<> set_server_init(http_context& ctx) {
|
||||
auto rb = std::make_shared < api_registry_builder > (ctx.api_doc);
|
||||
auto rb02 = std::make_shared < api_registry_builder20 > (ctx.api_doc, "/v2");
|
||||
|
||||
return ctx.http_server.set_routes([rb, &ctx, rb02](routes& r) {
|
||||
return ctx.http_server.set_routes([rb, &ctx](routes& r) {
|
||||
r.register_exeption_handler(exception_reply);
|
||||
r.put(GET, "/ui", new httpd::file_handler(ctx.api_dir + "/index.html",
|
||||
new content_replace("html")));
|
||||
r.add(GET, url("/ui").remainder("path"), new httpd::directory_handler(ctx.api_dir,
|
||||
new content_replace("html")));
|
||||
rb->set_api_doc(r);
|
||||
rb02->set_api_doc(r);
|
||||
rb02->register_api_file(r, "swagger20_header");
|
||||
rb->register_function(r, "system",
|
||||
"The system related API");
|
||||
set_system(ctx, r);
|
||||
@@ -115,11 +112,6 @@ future<> set_server_stream_manager(http_context& ctx) {
|
||||
"The stream manager API", set_stream_manager);
|
||||
}
|
||||
|
||||
future<> set_server_cache(http_context& ctx) {
|
||||
return register_api(ctx, "cache_service",
|
||||
"The cache service API", set_cache_service);
|
||||
}
|
||||
|
||||
future<> set_server_gossip_settle(http_context& ctx) {
|
||||
auto rb = std::make_shared < api_registry_builder > (ctx.api_doc);
|
||||
|
||||
@@ -127,6 +119,9 @@ future<> set_server_gossip_settle(http_context& ctx) {
|
||||
rb->register_function(r, "failure_detector",
|
||||
"The failure detector API");
|
||||
set_failure_detector(ctx,r);
|
||||
rb->register_function(r, "cache_service",
|
||||
"The cache service API");
|
||||
set_cache_service(ctx,r);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -46,7 +46,7 @@ future<> set_server_messaging_service(http_context& ctx);
|
||||
future<> set_server_storage_proxy(http_context& ctx);
|
||||
future<> set_server_stream_manager(http_context& ctx);
|
||||
future<> set_server_gossip_settle(http_context& ctx);
|
||||
future<> set_server_cache(http_context& ctx);
|
||||
future<> set_server_done(http_context& ctx);
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -93,13 +93,10 @@ void set_storage_service(http_context& ctx, routes& r) {
|
||||
return ctx.db.local().commitlog()->active_config().commit_log_location;
|
||||
});
|
||||
|
||||
ss::get_token_endpoint.set(r, [] (std::unique_ptr<request> req) {
|
||||
return make_ready_future<json::json_return_type>(stream_range_as_array(service::get_local_storage_service().get_token_to_endpoint_map(), [](const auto& i) {
|
||||
storage_service_json::mapper val;
|
||||
val.key = boost::lexical_cast<std::string>(i.first);
|
||||
val.value = boost::lexical_cast<std::string>(i.second);
|
||||
return val;
|
||||
}));
|
||||
ss::get_token_endpoint.set(r, [] (const_req req) {
|
||||
auto token_to_ep = service::get_local_storage_service().get_token_to_endpoint_map();
|
||||
std::vector<storage_service_json::mapper> res;
|
||||
return map_to_key_value(token_to_ep, res);
|
||||
});
|
||||
|
||||
ss::get_leaving_nodes.set(r, [](const_req req) {
|
||||
@@ -358,12 +355,6 @@ void set_storage_service(http_context& ctx, routes& r) {
|
||||
});
|
||||
});
|
||||
|
||||
ss::get_active_repair_async.set(r, [&ctx](std::unique_ptr<request> req) {
|
||||
return get_active_repairs(ctx.db).then([] (std::vector<int> res){
|
||||
return make_ready_future<json::json_return_type>(res);
|
||||
});
|
||||
});
|
||||
|
||||
ss::repair_async_status.set(r, [&ctx](std::unique_ptr<request> req) {
|
||||
return repair_get_status(ctx.db, boost::lexical_cast<int>( req->get_query_param("id")))
|
||||
.then_wrapped([] (future<repair_status>&& fut) {
|
||||
|
||||
@@ -57,6 +57,7 @@ class atomic_cell_type final {
|
||||
private:
|
||||
static constexpr int8_t LIVE_FLAG = 0x01;
|
||||
static constexpr int8_t EXPIRY_FLAG = 0x02; // When present, expiry field is present. Set only for live cells
|
||||
static constexpr int8_t REVERT_FLAG = 0x04; // transient flag used to efficiently implement ReversiblyMergeable for atomic cells.
|
||||
static constexpr int8_t COUNTER_UPDATE_FLAG = 0x08; // Cell is a counter update.
|
||||
static constexpr int8_t COUNTER_IN_PLACE_REVERT = 0x10;
|
||||
static constexpr unsigned flags_size = 1;
|
||||
@@ -73,10 +74,17 @@ private:
|
||||
static bool is_counter_update(bytes_view cell) {
|
||||
return cell[0] & COUNTER_UPDATE_FLAG;
|
||||
}
|
||||
static bool is_revert_set(bytes_view cell) {
|
||||
return cell[0] & REVERT_FLAG;
|
||||
}
|
||||
static bool is_counter_in_place_revert_set(bytes_view cell) {
|
||||
return cell[0] & COUNTER_IN_PLACE_REVERT;
|
||||
}
|
||||
template<typename BytesContainer>
|
||||
static void set_revert(BytesContainer& cell, bool revert) {
|
||||
cell[0] = (cell[0] & ~REVERT_FLAG) | (revert * REVERT_FLAG);
|
||||
}
|
||||
template<typename BytesContainer>
|
||||
static void set_counter_in_place_revert(BytesContainer& cell, bool flag) {
|
||||
cell[0] = (cell[0] & ~COUNTER_IN_PLACE_REVERT) | (flag * COUNTER_IN_PLACE_REVERT);
|
||||
}
|
||||
@@ -208,6 +216,9 @@ public:
|
||||
bool is_counter_update() const {
|
||||
return atomic_cell_type::is_counter_update(_data);
|
||||
}
|
||||
bool is_revert_set() const {
|
||||
return atomic_cell_type::is_revert_set(_data);
|
||||
}
|
||||
bool is_counter_in_place_revert_set() const {
|
||||
return atomic_cell_type::is_counter_in_place_revert_set(_data);
|
||||
}
|
||||
@@ -263,6 +274,9 @@ public:
|
||||
bytes_view serialize() const {
|
||||
return _data;
|
||||
}
|
||||
void set_revert(bool revert) {
|
||||
atomic_cell_type::set_revert(_data, revert);
|
||||
}
|
||||
void set_counter_in_place_revert(bool flag) {
|
||||
atomic_cell_type::set_counter_in_place_revert(_data, flag);
|
||||
}
|
||||
|
||||
@@ -25,7 +25,6 @@
|
||||
|
||||
#include "types.hh"
|
||||
#include "atomic_cell.hh"
|
||||
#include "atomic_cell_or_collection.hh"
|
||||
#include "hashing.hh"
|
||||
#include "counters.hh"
|
||||
|
||||
@@ -79,15 +78,3 @@ struct appending_hash<collection_mutation> {
|
||||
feed_hash(h, static_cast<collection_mutation_view>(cm), cdef);
|
||||
}
|
||||
};
|
||||
|
||||
template<>
|
||||
struct appending_hash<atomic_cell_or_collection> {
|
||||
template<typename Hasher>
|
||||
void operator()(Hasher& h, const atomic_cell_or_collection& c, const column_definition& cdef) const {
|
||||
if (cdef.is_atomic()) {
|
||||
feed_hash(h, c.as_atomic_cell(), cdef);
|
||||
} else {
|
||||
feed_hash(h, c.as_collection_mutation(), cdef);
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -59,6 +59,14 @@ public:
|
||||
bool operator==(const atomic_cell_or_collection& other) const {
|
||||
return _data == other._data;
|
||||
}
|
||||
template<typename Hasher>
|
||||
void feed_hash(Hasher& h, const column_definition& def) const {
|
||||
if (def.is_atomic()) {
|
||||
::feed_hash(h, as_atomic_cell(), def);
|
||||
} else {
|
||||
::feed_hash(h, as_collection_mutation(), def);
|
||||
}
|
||||
}
|
||||
size_t external_memory_usage() const {
|
||||
return _data.external_memory_usage();
|
||||
}
|
||||
|
||||
@@ -23,8 +23,8 @@
|
||||
|
||||
#include <stdexcept>
|
||||
|
||||
#include "auth/authenticated_user.hh"
|
||||
#include "auth/authenticator.hh"
|
||||
#include "auth/authenticated_user.hh"
|
||||
#include "auth/common.hh"
|
||||
|
||||
namespace cql3 {
|
||||
@@ -44,56 +44,52 @@ public:
|
||||
allow_all_authenticator(cql3::query_processor&, ::service::migration_manager&) {
|
||||
}
|
||||
|
||||
virtual future<> start() override {
|
||||
future<> start() override {
|
||||
return make_ready_future<>();
|
||||
}
|
||||
|
||||
virtual future<> stop() override {
|
||||
future<> stop() override {
|
||||
return make_ready_future<>();
|
||||
}
|
||||
|
||||
virtual const sstring& qualified_java_name() const override {
|
||||
const sstring& qualified_java_name() const override {
|
||||
return allow_all_authenticator_name();
|
||||
}
|
||||
|
||||
virtual bool require_authentication() const override {
|
||||
bool require_authentication() const override {
|
||||
return false;
|
||||
}
|
||||
|
||||
virtual authentication_option_set supported_options() const override {
|
||||
return authentication_option_set();
|
||||
option_set supported_options() const override {
|
||||
return option_set();
|
||||
}
|
||||
|
||||
virtual authentication_option_set alterable_options() const override {
|
||||
return authentication_option_set();
|
||||
option_set alterable_options() const override {
|
||||
return option_set();
|
||||
}
|
||||
|
||||
future<authenticated_user> authenticate(const credentials_map& credentials) const override {
|
||||
return make_ready_future<authenticated_user>(anonymous_user());
|
||||
future<::shared_ptr<authenticated_user>> authenticate(const credentials_map& credentials) const override {
|
||||
return make_ready_future<::shared_ptr<authenticated_user>>(::make_shared<authenticated_user>());
|
||||
}
|
||||
|
||||
virtual future<> create(stdx::string_view, const authentication_options& options) const override {
|
||||
future<> create(sstring username, const option_map& options) override {
|
||||
return make_ready_future();
|
||||
}
|
||||
|
||||
virtual future<> alter(stdx::string_view, const authentication_options& options) const override {
|
||||
future<> alter(sstring username, const option_map& options) override {
|
||||
return make_ready_future();
|
||||
}
|
||||
|
||||
virtual future<> drop(stdx::string_view) const override {
|
||||
future<> drop(sstring username) override {
|
||||
return make_ready_future();
|
||||
}
|
||||
|
||||
virtual future<custom_options> query_custom_options(stdx::string_view role_name) const override {
|
||||
return make_ready_future<custom_options>();
|
||||
const resource_ids& protected_resources() const override {
|
||||
static const resource_ids ids;
|
||||
return ids;
|
||||
}
|
||||
|
||||
virtual const resource_set& protected_resources() const override {
|
||||
static const resource_set resources;
|
||||
return resources;
|
||||
}
|
||||
|
||||
virtual ::shared_ptr<sasl_challenge> new_sasl_challenge() const override {
|
||||
::shared_ptr<sasl_challenge> new_sasl_challenge() const override {
|
||||
throw std::runtime_error("Should not reach");
|
||||
}
|
||||
};
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "auth/authorizer.hh"
|
||||
#include "authorizer.hh"
|
||||
#include "exceptions/exceptions.hh"
|
||||
#include "stdx.hh"
|
||||
|
||||
@@ -35,6 +35,8 @@ class migration_manager;
|
||||
|
||||
namespace auth {
|
||||
|
||||
class service;
|
||||
|
||||
const sstring& allow_all_authorizer_name();
|
||||
|
||||
class allow_all_authorizer final : public authorizer {
|
||||
@@ -42,51 +44,54 @@ public:
|
||||
allow_all_authorizer(cql3::query_processor&, ::service::migration_manager&) {
|
||||
}
|
||||
|
||||
virtual future<> start() override {
|
||||
future<> start() override {
|
||||
return make_ready_future<>();
|
||||
}
|
||||
|
||||
virtual future<> stop() override {
|
||||
future<> stop() override {
|
||||
return make_ready_future<>();
|
||||
}
|
||||
|
||||
virtual const sstring& qualified_java_name() const override {
|
||||
const sstring& qualified_java_name() const override {
|
||||
return allow_all_authorizer_name();
|
||||
}
|
||||
|
||||
virtual future<permission_set> authorize(const role_or_anonymous&, const resource&) const override {
|
||||
future<permission_set> authorize(service&, ::shared_ptr<authenticated_user>, data_resource) const override {
|
||||
return make_ready_future<permission_set>(permissions::ALL);
|
||||
}
|
||||
|
||||
virtual future<> grant(stdx::string_view, permission_set, const resource&) const override {
|
||||
return make_exception_future<>(
|
||||
unsupported_authorization_operation("GRANT operation is not supported by AllowAllAuthorizer"));
|
||||
future<> grant(::shared_ptr<authenticated_user>, permission_set, data_resource, sstring) override {
|
||||
throw exceptions::invalid_request_exception("GRANT operation is not supported by AllowAllAuthorizer");
|
||||
}
|
||||
|
||||
virtual future<> revoke(stdx::string_view, permission_set, const resource&) const override {
|
||||
return make_exception_future<>(
|
||||
unsupported_authorization_operation("REVOKE operation is not supported by AllowAllAuthorizer"));
|
||||
future<> revoke(::shared_ptr<authenticated_user>, permission_set, data_resource, sstring) override {
|
||||
throw exceptions::invalid_request_exception("REVOKE operation is not supported by AllowAllAuthorizer");
|
||||
}
|
||||
|
||||
virtual future<std::vector<permission_details>> list_all() const override {
|
||||
return make_exception_future<std::vector<permission_details>>(
|
||||
unsupported_authorization_operation(
|
||||
"LIST PERMISSIONS operation is not supported by AllowAllAuthorizer"));
|
||||
future<std::vector<permission_details>> list(
|
||||
service&,
|
||||
::shared_ptr<authenticated_user> performer,
|
||||
permission_set,
|
||||
stdx::optional<data_resource>,
|
||||
stdx::optional<sstring>) const override {
|
||||
throw exceptions::invalid_request_exception("LIST PERMISSIONS operation is not supported by AllowAllAuthorizer");
|
||||
}
|
||||
|
||||
virtual future<> revoke_all(stdx::string_view) const override {
|
||||
return make_exception_future(
|
||||
unsupported_authorization_operation("REVOKE operation is not supported by AllowAllAuthorizer"));
|
||||
future<> revoke_all(sstring dropped_user) override {
|
||||
return make_ready_future();
|
||||
}
|
||||
|
||||
virtual future<> revoke_all(const resource&) const override {
|
||||
return make_exception_future(
|
||||
unsupported_authorization_operation("REVOKE operation is not supported by AllowAllAuthorizer"));
|
||||
future<> revoke_all(data_resource) override {
|
||||
return make_ready_future();
|
||||
}
|
||||
|
||||
virtual const resource_set& protected_resources() const override {
|
||||
static const resource_set resources;
|
||||
return resources;
|
||||
const resource_ids& protected_resources() override {
|
||||
static const resource_ids ids;
|
||||
return ids;
|
||||
}
|
||||
|
||||
future<> validate_configuration() const override {
|
||||
return make_ready_future();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -39,30 +39,26 @@
|
||||
* along with Scylla. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "auth/authenticated_user.hh"
|
||||
|
||||
#include <iostream>
|
||||
#include "authenticated_user.hh"
|
||||
|
||||
namespace auth {
|
||||
const sstring auth::authenticated_user::ANONYMOUS_USERNAME("anonymous");
|
||||
|
||||
authenticated_user::authenticated_user(stdx::string_view name)
|
||||
: name(sstring(name)) {
|
||||
}
|
||||
|
||||
std::ostream& operator<<(std::ostream& os, const authenticated_user& u) {
|
||||
if (!u.name) {
|
||||
os << "anonymous";
|
||||
} else {
|
||||
os << *u.name;
|
||||
}
|
||||
|
||||
return os;
|
||||
}
|
||||
|
||||
static const authenticated_user the_anonymous_user{};
|
||||
|
||||
const authenticated_user& anonymous_user() noexcept {
|
||||
return the_anonymous_user;
|
||||
auth::authenticated_user::authenticated_user()
|
||||
: _anon(true)
|
||||
{}
|
||||
|
||||
auth::authenticated_user::authenticated_user(sstring name)
|
||||
: _name(name), _anon(false)
|
||||
{}
|
||||
|
||||
auth::authenticated_user::authenticated_user(authenticated_user&&) = default;
|
||||
auth::authenticated_user::authenticated_user(const authenticated_user&) = default;
|
||||
|
||||
const sstring& auth::authenticated_user::name() const {
|
||||
return _anon ? ANONYMOUS_USERNAME : _name;
|
||||
}
|
||||
|
||||
bool auth::authenticated_user::operator==(const authenticated_user& v) const {
|
||||
return _anon ? v._anon : _name == v._name;
|
||||
}
|
||||
|
||||
@@ -41,63 +41,35 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <experimental/string_view>
|
||||
#include <functional>
|
||||
#include <iosfwd>
|
||||
#include <optional>
|
||||
|
||||
#include <seastar/core/sstring.hh>
|
||||
|
||||
#include <seastar/core/future.hh>
|
||||
#include "seastarx.hh"
|
||||
#include "stdx.hh"
|
||||
|
||||
namespace auth {
|
||||
|
||||
///
|
||||
/// A type-safe wrapper for the name of a logged-in user, or a nameless (anonymous) user.
|
||||
///
|
||||
class authenticated_user final {
|
||||
class authenticated_user {
|
||||
public:
|
||||
///
|
||||
/// An anonymous user has no name.
|
||||
///
|
||||
std::optional<sstring> name{};
|
||||
static const sstring ANONYMOUS_USERNAME;
|
||||
|
||||
///
|
||||
/// An anonymous user.
|
||||
///
|
||||
authenticated_user() = default;
|
||||
explicit authenticated_user(stdx::string_view name);
|
||||
};
|
||||
authenticated_user();
|
||||
authenticated_user(sstring name);
|
||||
authenticated_user(authenticated_user&&);
|
||||
authenticated_user(const authenticated_user&);
|
||||
|
||||
///
|
||||
/// The user name, or "anonymous".
|
||||
///
|
||||
std::ostream& operator<<(std::ostream&, const authenticated_user&);
|
||||
const sstring& name() const;
|
||||
|
||||
inline bool operator==(const authenticated_user& u1, const authenticated_user& u2) noexcept {
|
||||
return u1.name == u2.name;
|
||||
}
|
||||
|
||||
inline bool operator!=(const authenticated_user& u1, const authenticated_user& u2) noexcept {
|
||||
return !(u1 == u2);
|
||||
}
|
||||
|
||||
const authenticated_user& anonymous_user() noexcept;
|
||||
|
||||
inline bool is_anonymous(const authenticated_user& u) noexcept {
|
||||
return u == anonymous_user();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
namespace std {
|
||||
|
||||
template <>
|
||||
struct hash<auth::authenticated_user> final {
|
||||
size_t operator()(const auth::authenticated_user &u) const {
|
||||
return std::hash<std::optional<sstring>>()(u.name);
|
||||
/**
|
||||
* If IAuthenticator doesn't require authentication, this method may return true.
|
||||
*/
|
||||
bool is_anonymous() const {
|
||||
return _anon;
|
||||
}
|
||||
|
||||
bool operator==(const authenticated_user&) const;
|
||||
private:
|
||||
sstring _name;
|
||||
bool _anon;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -1,64 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2018 ScyllaDB
|
||||
*/
|
||||
|
||||
/*
|
||||
* This file is part of Scylla.
|
||||
*
|
||||
* Scylla is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Scylla is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Scylla. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <iosfwd>
|
||||
#include <optional>
|
||||
#include <stdexcept>
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
|
||||
#include <seastar/core/print.hh>
|
||||
#include <seastar/core/sstring.hh>
|
||||
|
||||
#include "seastarx.hh"
|
||||
|
||||
namespace auth {
|
||||
|
||||
enum class authentication_option {
|
||||
password,
|
||||
options
|
||||
};
|
||||
|
||||
std::ostream& operator<<(std::ostream&, authentication_option);
|
||||
|
||||
using authentication_option_set = std::unordered_set<authentication_option>;
|
||||
|
||||
using custom_options = std::unordered_map<sstring, sstring>;
|
||||
|
||||
struct authentication_options final {
|
||||
std::optional<sstring> password;
|
||||
std::optional<custom_options> options;
|
||||
};
|
||||
|
||||
inline bool any_authentication_options(const authentication_options& aos) noexcept {
|
||||
return aos.password || aos.options;
|
||||
}
|
||||
|
||||
class unsupported_authentication_option : public std::invalid_argument {
|
||||
public:
|
||||
explicit unsupported_authentication_option(authentication_option k)
|
||||
: std::invalid_argument(sprint("The %s option is not supported.", k)) {
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
@@ -39,14 +39,29 @@
|
||||
* along with Scylla. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "auth/authenticator.hh"
|
||||
|
||||
#include "auth/authenticated_user.hh"
|
||||
#include "auth/common.hh"
|
||||
#include "auth/password_authenticator.hh"
|
||||
#include "authenticator.hh"
|
||||
#include "authenticated_user.hh"
|
||||
#include "common.hh"
|
||||
#include "password_authenticator.hh"
|
||||
#include "cql3/query_processor.hh"
|
||||
#include "db/config.hh"
|
||||
#include "utils/class_registrator.hh"
|
||||
|
||||
const sstring auth::authenticator::USERNAME_KEY("username");
|
||||
const sstring auth::authenticator::PASSWORD_KEY("password");
|
||||
|
||||
auth::authenticator::option auth::authenticator::string_to_option(const sstring& name) {
|
||||
if (strcasecmp(name.c_str(), "password") == 0) {
|
||||
return option::PASSWORD;
|
||||
}
|
||||
throw std::invalid_argument(name);
|
||||
}
|
||||
|
||||
sstring auth::authenticator::option_to_string(option opt) {
|
||||
switch (opt) {
|
||||
case option::PASSWORD:
|
||||
return "PASSWORD";
|
||||
default:
|
||||
throw std::invalid_argument(sprint("Unknown option {}", opt));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,24 +41,21 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <experimental/string_view>
|
||||
#include <memory>
|
||||
#include <unordered_map>
|
||||
#include <set>
|
||||
#include <stdexcept>
|
||||
#include <unordered_map>
|
||||
|
||||
#include <boost/any.hpp>
|
||||
#include <seastar/core/enum.hh>
|
||||
#include <seastar/core/future.hh>
|
||||
#include <seastar/core/sstring.hh>
|
||||
#include <seastar/core/shared_ptr.hh>
|
||||
|
||||
#include "auth/authentication_options.hh"
|
||||
#include "auth/resource.hh"
|
||||
#include <seastar/core/sstring.hh>
|
||||
#include <seastar/core/future.hh>
|
||||
#include <seastar/core/shared_ptr.hh>
|
||||
#include <seastar/core/enum.hh>
|
||||
|
||||
#include "bytes.hh"
|
||||
#include "data_resource.hh"
|
||||
#include "enum_set.hh"
|
||||
#include "exceptions/exceptions.hh"
|
||||
#include "stdx.hh"
|
||||
|
||||
namespace db {
|
||||
class config;
|
||||
@@ -68,104 +65,126 @@ namespace auth {
|
||||
|
||||
class authenticated_user;
|
||||
|
||||
///
|
||||
/// Abstract client for authenticating role identity.
|
||||
///
|
||||
/// All state necessary to authorize a role is stored externally to the client instance.
|
||||
///
|
||||
class authenticator {
|
||||
public:
|
||||
///
|
||||
/// The name of the key to be used for the user-name part of password authentication with \ref authenticate.
|
||||
///
|
||||
static const sstring USERNAME_KEY;
|
||||
|
||||
///
|
||||
/// The name of the key to be used for the password part of password authentication with \ref authenticate.
|
||||
///
|
||||
static const sstring PASSWORD_KEY;
|
||||
|
||||
/**
|
||||
* Supported CREATE USER/ALTER USER options.
|
||||
* Currently only PASSWORD is available.
|
||||
*/
|
||||
enum class option {
|
||||
PASSWORD
|
||||
};
|
||||
|
||||
static option string_to_option(const sstring&);
|
||||
static sstring option_to_string(option);
|
||||
|
||||
using option_set = enum_set<super_enum<option, option::PASSWORD>>;
|
||||
using option_map = std::unordered_map<option, boost::any, enum_hash<option>>;
|
||||
using credentials_map = std::unordered_map<sstring, sstring>;
|
||||
|
||||
virtual ~authenticator() = default;
|
||||
virtual ~authenticator()
|
||||
{}
|
||||
|
||||
virtual future<> start() = 0;
|
||||
|
||||
virtual future<> stop() = 0;
|
||||
|
||||
///
|
||||
/// A fully-qualified (class with package) Java-like name for this implementation.
|
||||
///
|
||||
virtual const sstring& qualified_java_name() const = 0;
|
||||
|
||||
/**
|
||||
* Whether or not the authenticator requires explicit login.
|
||||
* If false will instantiate user with AuthenticatedUser.ANONYMOUS_USER.
|
||||
*/
|
||||
virtual bool require_authentication() const = 0;
|
||||
|
||||
virtual authentication_option_set supported_options() const = 0;
|
||||
/**
|
||||
* Set of options supported by CREATE USER and ALTER USER queries.
|
||||
* Should never return null - always return an empty set instead.
|
||||
*/
|
||||
virtual option_set supported_options() const = 0;
|
||||
|
||||
///
|
||||
/// A subset of `supported_options()` that users are permitted to alter for themselves.
|
||||
///
|
||||
virtual authentication_option_set alterable_options() const = 0;
|
||||
/**
|
||||
* Subset of supportedOptions that users are allowed to alter when performing ALTER USER [themselves].
|
||||
* Should never return null - always return an empty set instead.
|
||||
*/
|
||||
virtual option_set alterable_options() const = 0;
|
||||
|
||||
///
|
||||
/// Authenticate a user given implementation-specific credentials.
|
||||
///
|
||||
/// If this implementation does not require authentication (\ref require_authentication), an anonymous user may
|
||||
/// result.
|
||||
///
|
||||
/// \returns an exceptional future with \ref exceptions::authentication_exception if given invalid credentials.
|
||||
///
|
||||
virtual future<authenticated_user> authenticate(const credentials_map& credentials) const = 0;
|
||||
/**
|
||||
* Authenticates a user given a Map<String, String> of credentials.
|
||||
* Should never return null - always throw AuthenticationException instead.
|
||||
* Returning AuthenticatedUser.ANONYMOUS_USER is an option as well if authentication is not required.
|
||||
*
|
||||
* @throws authentication_exception if credentials don't match any known user.
|
||||
*/
|
||||
virtual future<::shared_ptr<authenticated_user>> authenticate(const credentials_map& credentials) const = 0;
|
||||
|
||||
///
|
||||
/// Create an authentication record for a new user. This is required before the user can log-in.
|
||||
///
|
||||
/// The options provided must be a subset of `supported_options()`.
|
||||
///
|
||||
virtual future<> create(stdx::string_view role_name, const authentication_options& options) const = 0;
|
||||
/**
|
||||
* Called during execution of CREATE USER query (also may be called on startup, see seedSuperuserOptions method).
|
||||
* If authenticator is static then the body of the method should be left blank, but don't throw an exception.
|
||||
* options are guaranteed to be a subset of supportedOptions().
|
||||
*
|
||||
* @param username Username of the user to create.
|
||||
* @param options Options the user will be created with.
|
||||
* @throws exceptions::request_validation_exception
|
||||
* @throws exceptions::request_execution_exception
|
||||
*/
|
||||
virtual future<> create(sstring username, const option_map& options) = 0;
|
||||
|
||||
///
|
||||
/// Alter the authentication record of an existing user.
|
||||
///
|
||||
/// The options provided must be a subset of `supported_options()`.
|
||||
///
|
||||
/// Callers must ensure that the specification of `alterable_options()` is adhered to.
|
||||
///
|
||||
virtual future<> alter(stdx::string_view role_name, const authentication_options& options) const = 0;
|
||||
/**
|
||||
* Called during execution of ALTER USER query.
|
||||
* options are always guaranteed to be a subset of supportedOptions(). Furthermore, if the user performing the query
|
||||
* is not a superuser and is altering himself, then options are guaranteed to be a subset of alterableOptions().
|
||||
* Keep the body of the method blank if your implementation doesn't support any options.
|
||||
*
|
||||
* @param username Username of the user that will be altered.
|
||||
* @param options Options to alter.
|
||||
* @throws exceptions::request_validation_exception
|
||||
* @throws exceptions::request_execution_exception
|
||||
*/
|
||||
virtual future<> alter(sstring username, const option_map& options) = 0;
|
||||
|
||||
///
|
||||
/// Delete the authentication record for a user. This will disallow the user from logging in.
|
||||
///
|
||||
virtual future<> drop(stdx::string_view role_name) const = 0;
|
||||
|
||||
///
|
||||
/// Query for custom options (those corresponding to \ref authentication_options::options).
|
||||
///
|
||||
/// If no options are set the result is an empty container.
|
||||
///
|
||||
virtual future<custom_options> query_custom_options(stdx::string_view role_name) const = 0;
|
||||
/**
|
||||
* Called during execution of DROP USER query.
|
||||
*
|
||||
* @param username Username of the user that will be dropped.
|
||||
* @throws exceptions::request_validation_exception
|
||||
* @throws exceptions::request_execution_exception
|
||||
*/
|
||||
virtual future<> drop(sstring username) = 0;
|
||||
|
||||
///
|
||||
/// System resources used internally as part of the implementation. These are made inaccessible to users.
|
||||
///
|
||||
virtual const resource_set& protected_resources() const = 0;
|
||||
/**
|
||||
* Set of resources that should be made inaccessible to users and only accessible internally.
|
||||
*
|
||||
* @return Keyspaces, column families that will be unmodifiable by users; other resources.
|
||||
* @see resource_ids
|
||||
*/
|
||||
virtual const resource_ids& protected_resources() const = 0;
|
||||
|
||||
///
|
||||
/// A stateful SASL challenge which supports many authentication schemes (depending on the implementation).
|
||||
///
|
||||
class sasl_challenge {
|
||||
public:
|
||||
virtual ~sasl_challenge() = default;
|
||||
|
||||
virtual ~sasl_challenge() {}
|
||||
virtual bytes evaluate_response(bytes_view client_response) = 0;
|
||||
|
||||
virtual bool is_complete() const = 0;
|
||||
|
||||
virtual future<authenticated_user> get_authenticated_user() const = 0;
|
||||
virtual future<::shared_ptr<authenticated_user>> get_authenticated_user() const = 0;
|
||||
};
|
||||
|
||||
/**
|
||||
* Provide a sasl_challenge to be used by the CQL binary protocol server. If
|
||||
* the configured authenticator requires authentication but does not implement this
|
||||
* interface we refuse to start the binary protocol server as it will have no way
|
||||
* of authenticating clients.
|
||||
* @return sasl_challenge implementation
|
||||
*/
|
||||
virtual ::shared_ptr<sasl_challenge> new_sasl_challenge() const = 0;
|
||||
};
|
||||
|
||||
inline std::ostream& operator<<(std::ostream& os, authenticator::option opt) {
|
||||
return os << authenticator::option_to_string(opt);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
118
auth/authorizer.cc
Normal file
118
auth/authorizer.cc
Normal file
@@ -0,0 +1,118 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Copyright (C) 2016 ScyllaDB
|
||||
*
|
||||
* Modified by ScyllaDB
|
||||
*/
|
||||
|
||||
/*
|
||||
* This file is part of Scylla.
|
||||
*
|
||||
* Scylla is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Scylla is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Scylla. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "authorizer.hh"
|
||||
#include "authenticated_user.hh"
|
||||
#include "common.hh"
|
||||
#include "default_authorizer.hh"
|
||||
#include "auth.hh"
|
||||
#include "cql3/query_processor.hh"
|
||||
#include "db/config.hh"
|
||||
#include "utils/class_registrator.hh"
|
||||
|
||||
const sstring& auth::allow_all_authorizer_name() {
|
||||
static const sstring name = meta::AUTH_PACKAGE_NAME + "AllowAllAuthorizer";
|
||||
return name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Authenticator is assumed to be a fully state-less immutable object (note all the const).
|
||||
* We thus store a single instance globally, since it should be safe/ok.
|
||||
*/
|
||||
static std::unique_ptr<auth::authorizer> global_authorizer;
|
||||
using authorizer_registry = class_registry<auth::authorizer, cql3::query_processor&>;
|
||||
|
||||
future<>
|
||||
auth::authorizer::setup(const sstring& type) {
|
||||
if (type == allow_all_authorizer_name()) {
|
||||
class allow_all_authorizer : public authorizer {
|
||||
public:
|
||||
future<> start() override {
|
||||
return make_ready_future<>();
|
||||
}
|
||||
future<> stop() override {
|
||||
return make_ready_future<>();
|
||||
}
|
||||
const sstring& qualified_java_name() const override {
|
||||
return allow_all_authorizer_name();
|
||||
}
|
||||
future<permission_set> authorize(::shared_ptr<authenticated_user>, data_resource) const override {
|
||||
return make_ready_future<permission_set>(permissions::ALL);
|
||||
}
|
||||
future<> grant(::shared_ptr<authenticated_user>, permission_set, data_resource, sstring) override {
|
||||
throw exceptions::invalid_request_exception("GRANT operation is not supported by AllowAllAuthorizer");
|
||||
}
|
||||
future<> revoke(::shared_ptr<authenticated_user>, permission_set, data_resource, sstring) override {
|
||||
throw exceptions::invalid_request_exception("REVOKE operation is not supported by AllowAllAuthorizer");
|
||||
}
|
||||
future<std::vector<permission_details>> list(::shared_ptr<authenticated_user> performer, permission_set, optional<data_resource>, optional<sstring>) const override {
|
||||
throw exceptions::invalid_request_exception("LIST PERMISSIONS operation is not supported by AllowAllAuthorizer");
|
||||
}
|
||||
future<> revoke_all(sstring dropped_user) override {
|
||||
return make_ready_future();
|
||||
}
|
||||
future<> revoke_all(data_resource) override {
|
||||
return make_ready_future();
|
||||
}
|
||||
const resource_ids& protected_resources() override {
|
||||
static const resource_ids ids;
|
||||
return ids;
|
||||
}
|
||||
future<> validate_configuration() const override {
|
||||
return make_ready_future();
|
||||
}
|
||||
};
|
||||
|
||||
global_authorizer = std::make_unique<allow_all_authorizer>();
|
||||
return make_ready_future();
|
||||
} else {
|
||||
auto a = authorizer_registry::create(type, cql3::get_local_query_processor());
|
||||
auto f = a->start();
|
||||
return f.then([a = std::move(a)]() mutable {
|
||||
global_authorizer = std::move(a);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
auth::authorizer& auth::authorizer::get() {
|
||||
assert(global_authorizer);
|
||||
return *global_authorizer;
|
||||
}
|
||||
@@ -41,116 +41,127 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <experimental/string_view>
|
||||
#include <functional>
|
||||
#include <optional>
|
||||
#include <stdexcept>
|
||||
#include <tuple>
|
||||
#include <vector>
|
||||
#include <tuple>
|
||||
|
||||
#include <experimental/optional>
|
||||
#include <seastar/core/future.hh>
|
||||
#include <seastar/core/shared_ptr.hh>
|
||||
|
||||
#include "auth/permission.hh"
|
||||
#include "auth/resource.hh"
|
||||
#include "permission.hh"
|
||||
#include "data_resource.hh"
|
||||
|
||||
#include "seastarx.hh"
|
||||
#include "stdx.hh"
|
||||
|
||||
namespace auth {
|
||||
|
||||
class role_or_anonymous;
|
||||
class service;
|
||||
|
||||
class authenticated_user;
|
||||
|
||||
struct permission_details {
|
||||
sstring role_name;
|
||||
::auth::resource resource;
|
||||
sstring user;
|
||||
data_resource resource;
|
||||
permission_set permissions;
|
||||
|
||||
bool operator<(const permission_details& v) const {
|
||||
return std::tie(user, resource, permissions) < std::tie(v.user, v.resource, v.permissions);
|
||||
}
|
||||
};
|
||||
|
||||
inline bool operator==(const permission_details& pd1, const permission_details& pd2) {
|
||||
return std::forward_as_tuple(pd1.role_name, pd1.resource, pd1.permissions.mask())
|
||||
== std::forward_as_tuple(pd2.role_name, pd2.resource, pd2.permissions.mask());
|
||||
}
|
||||
using std::experimental::optional;
|
||||
|
||||
inline bool operator!=(const permission_details& pd1, const permission_details& pd2) {
|
||||
return !(pd1 == pd2);
|
||||
}
|
||||
|
||||
inline bool operator<(const permission_details& pd1, const permission_details& pd2) {
|
||||
return std::forward_as_tuple(pd1.role_name, pd1.resource, pd1.permissions)
|
||||
< std::forward_as_tuple(pd2.role_name, pd2.resource, pd2.permissions);
|
||||
}
|
||||
|
||||
class unsupported_authorization_operation : public std::invalid_argument {
|
||||
public:
|
||||
using std::invalid_argument::invalid_argument;
|
||||
};
|
||||
|
||||
///
|
||||
/// Abstract client for authorizing roles to access resources.
|
||||
///
|
||||
/// All state necessary to authorize a role is stored externally to the client instance.
|
||||
///
|
||||
class authorizer {
|
||||
public:
|
||||
virtual ~authorizer() = default;
|
||||
virtual ~authorizer() {}
|
||||
|
||||
virtual future<> start() = 0;
|
||||
|
||||
virtual future<> stop() = 0;
|
||||
|
||||
///
|
||||
/// A fully-qualified (class with package) Java-like name for this implementation.
|
||||
///
|
||||
virtual const sstring& qualified_java_name() const = 0;
|
||||
|
||||
///
|
||||
/// Query for the permissions granted directly to a role for a particular \ref resource (and not any of its
|
||||
/// parents).
|
||||
///
|
||||
/// The optional role name is empty when an anonymous user is authorized. Some implementations may still wish to
|
||||
/// grant default permissions in this case.
|
||||
///
|
||||
virtual future<permission_set> authorize(const role_or_anonymous&, const resource&) const = 0;
|
||||
/**
|
||||
* The primary Authorizer method. Returns a set of permissions of a user on a resource.
|
||||
*
|
||||
* @param user Authenticated user requesting authorization.
|
||||
* @param resource Resource for which the authorization is being requested. @see DataResource.
|
||||
* @return Set of permissions of the user on the resource. Should never return empty. Use permission.NONE instead.
|
||||
*/
|
||||
virtual future<permission_set> authorize(service&, ::shared_ptr<authenticated_user>, data_resource) const = 0;
|
||||
|
||||
///
|
||||
/// Grant a set of permissions to a role for a particular \ref resource.
|
||||
///
|
||||
/// \throws \ref unsupported_authorization_operation if granting permissions is not supported.
|
||||
///
|
||||
virtual future<> grant(stdx::string_view role_name, permission_set, const resource&) const = 0;
|
||||
/**
|
||||
* Grants a set of permissions on a resource to a user.
|
||||
* The opposite of revoke().
|
||||
*
|
||||
* @param performer User who grants the permissions.
|
||||
* @param permissions Set of permissions to grant.
|
||||
* @param to Grantee of the permissions.
|
||||
* @param resource Resource on which to grant the permissions.
|
||||
*
|
||||
* @throws RequestValidationException
|
||||
* @throws RequestExecutionException
|
||||
*/
|
||||
virtual future<> grant(::shared_ptr<authenticated_user> performer, permission_set, data_resource, sstring to) = 0;
|
||||
|
||||
///
|
||||
/// Revoke a set of permissions from a role for a particular \ref resource.
|
||||
///
|
||||
/// \throws \ref unsupported_authorization_operation if revoking permissions is not supported.
|
||||
///
|
||||
virtual future<> revoke(stdx::string_view role_name, permission_set, const resource&) const = 0;
|
||||
/**
|
||||
* Revokes a set of permissions on a resource from a user.
|
||||
* The opposite of grant().
|
||||
*
|
||||
* @param performer User who revokes the permissions.
|
||||
* @param permissions Set of permissions to revoke.
|
||||
* @param from Revokee of the permissions.
|
||||
* @param resource Resource on which to revoke the permissions.
|
||||
*
|
||||
* @throws RequestValidationException
|
||||
* @throws RequestExecutionException
|
||||
*/
|
||||
virtual future<> revoke(::shared_ptr<authenticated_user> performer, permission_set, data_resource, sstring from) = 0;
|
||||
|
||||
///
|
||||
/// Query for all directly granted permissions.
|
||||
///
|
||||
/// \throws \ref unsupported_authorization_operation if listing permissions is not supported.
|
||||
///
|
||||
virtual future<std::vector<permission_details>> list_all() const = 0;
|
||||
/**
|
||||
* Returns a list of permissions on a resource of a user.
|
||||
*
|
||||
* @param performer User who wants to see the permissions.
|
||||
* @param permissions Set of Permission values the user is interested in. The result should only include the matching ones.
|
||||
* @param resource The resource on which permissions are requested. Can be null, in which case permissions on all resources
|
||||
* should be returned.
|
||||
* @param of The user whose permissions are requested. Can be null, in which case permissions of every user should be returned.
|
||||
*
|
||||
* @return All of the matching permission that the requesting user is authorized to know about.
|
||||
*
|
||||
* @throws RequestValidationException
|
||||
* @throws RequestExecutionException
|
||||
*/
|
||||
virtual future<std::vector<permission_details>> list(service&, ::shared_ptr<authenticated_user> performer, permission_set, optional<data_resource>, optional<sstring>) const = 0;
|
||||
|
||||
///
|
||||
/// Revoke all permissions granted directly to a particular role.
|
||||
///
|
||||
/// \throws \ref unsupported_authorization_operation if revoking permissions is not supported.
|
||||
///
|
||||
virtual future<> revoke_all(stdx::string_view role_name) const = 0;
|
||||
/**
|
||||
* This method is called before deleting a user with DROP USER query so that a new user with the same
|
||||
* name wouldn't inherit permissions of the deleted user in the future.
|
||||
*
|
||||
* @param droppedUser The user to revoke all permissions from.
|
||||
*/
|
||||
virtual future<> revoke_all(sstring dropped_user) = 0;
|
||||
|
||||
///
|
||||
/// Revoke all permissions granted to any role for a particular resource.
|
||||
///
|
||||
/// \throws \ref unsupported_authorization_operation if revoking permissions is not supported.
|
||||
///
|
||||
virtual future<> revoke_all(const resource&) const = 0;
|
||||
/**
|
||||
* This method is called after a resource is removed (i.e. keyspace or a table is dropped).
|
||||
*
|
||||
* @param droppedResource The resource to revoke all permissions on.
|
||||
*/
|
||||
virtual future<> revoke_all(data_resource) = 0;
|
||||
|
||||
///
|
||||
/// System resources used internally as part of the implementation. These are made inaccessible to users.
|
||||
///
|
||||
virtual const resource_set& protected_resources() const = 0;
|
||||
/**
|
||||
* Set of resources that should be made inaccessible to users and only accessible internally.
|
||||
*
|
||||
* @return Keyspaces, column families that will be unmodifiable by users; other resources.
|
||||
*/
|
||||
virtual const resource_ids& protected_resources() = 0;
|
||||
|
||||
/**
|
||||
* Validates configuration of IAuthorizer implementation (if configurable).
|
||||
*
|
||||
* @throws ConfigurationException when there is a configuration error.
|
||||
*/
|
||||
virtual future<> validate_configuration() const = 0;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -25,7 +25,6 @@
|
||||
|
||||
#include "cql3/query_processor.hh"
|
||||
#include "cql3/statements/create_table_statement.hh"
|
||||
#include "database.hh"
|
||||
#include "schema_builder.hh"
|
||||
#include "service/migration_manager.hh"
|
||||
|
||||
@@ -40,32 +39,14 @@ const sstring AUTH_PACKAGE_NAME("org.apache.cassandra.auth.");
|
||||
|
||||
}
|
||||
|
||||
static logging::logger auth_log("auth");
|
||||
|
||||
// Func must support being invoked more than once.
|
||||
future<> do_after_system_ready(seastar::abort_source& as, seastar::noncopyable_function<future<>()> func) {
|
||||
struct empty_state { };
|
||||
return delay_until_system_ready(as).then([&as, func = std::move(func)] () mutable {
|
||||
return exponential_backoff_retry::do_until_value(1s, 1min, as, [func = std::move(func)] {
|
||||
return func().then_wrapped([] (auto&& f) -> stdx::optional<empty_state> {
|
||||
if (f.failed()) {
|
||||
auth_log.info("Auth task failed with error, rescheduling: {}", f.get_exception());
|
||||
return { };
|
||||
}
|
||||
return { empty_state() };
|
||||
});
|
||||
});
|
||||
}).discard_result();
|
||||
}
|
||||
|
||||
future<> create_metadata_table_if_missing(
|
||||
stdx::string_view table_name,
|
||||
const sstring& table_name,
|
||||
cql3::query_processor& qp,
|
||||
stdx::string_view cql,
|
||||
const sstring& cql,
|
||||
::service::migration_manager& mm) {
|
||||
auto& db = qp.db().local();
|
||||
|
||||
if (db.has_schema(meta::AUTH_KS, sstring(table_name))) {
|
||||
if (db.has_schema(meta::AUTH_KS, table_name)) {
|
||||
return make_ready_future<>();
|
||||
}
|
||||
|
||||
@@ -77,7 +58,7 @@ future<> create_metadata_table_if_missing(
|
||||
auto statement = static_pointer_cast<cql3::statements::create_table_statement>(
|
||||
parsed_statement->prepare(db, qp.get_cql_stats())->statement);
|
||||
|
||||
const auto schema = statement->get_cf_meta_data(qp.db().local());
|
||||
const auto schema = statement->get_cf_meta_data();
|
||||
const auto uuid = generate_legacy_id(schema->ks_name(), schema->cf_name());
|
||||
|
||||
schema_builder b(schema);
|
||||
@@ -86,12 +67,4 @@ future<> create_metadata_table_if_missing(
|
||||
return mm.announce_new_column_family(b.build(), false);
|
||||
}
|
||||
|
||||
future<> wait_for_schema_agreement(::service::migration_manager& mm, const database& db) {
|
||||
static const auto pause = [] { return sleep(std::chrono::milliseconds(500)); };
|
||||
|
||||
return do_until([&db] { return db.get_version() != database::empty_version; }, pause).then([&mm] {
|
||||
return do_until([&mm] { return mm.have_schema_agreement(); }, pause);
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -22,22 +22,14 @@
|
||||
#pragma once
|
||||
|
||||
#include <chrono>
|
||||
#include <experimental/string_view>
|
||||
|
||||
#include <seastar/core/future.hh>
|
||||
#include <seastar/core/abort_source.hh>
|
||||
#include <seastar/util/noncopyable_function.hh>
|
||||
#include <seastar/core/reactor.hh>
|
||||
#include <seastar/core/resource.hh>
|
||||
#include <seastar/core/sstring.hh>
|
||||
|
||||
#include "log.hh"
|
||||
#include "delayed_tasks.hh"
|
||||
#include "seastarx.hh"
|
||||
#include "utils/exponential_backoff_retry.hh"
|
||||
|
||||
using namespace std::chrono_literals;
|
||||
|
||||
class database;
|
||||
|
||||
namespace service {
|
||||
class migration_manager;
|
||||
@@ -67,19 +59,16 @@ future<> once_among_shards(Task&& f) {
|
||||
return make_ready_future<>();
|
||||
}
|
||||
|
||||
inline future<> delay_until_system_ready(seastar::abort_source& as) {
|
||||
return sleep_abortable(15s, as);
|
||||
template <class Task, class Clock>
|
||||
void delay_until_system_ready(delayed_tasks<Clock>& ts, Task&& f) {
|
||||
static const typename std::chrono::milliseconds delay_duration(10000);
|
||||
ts.schedule_after(delay_duration, std::forward<Task>(f));
|
||||
}
|
||||
|
||||
// Func must support being invoked more than once.
|
||||
future<> do_after_system_ready(seastar::abort_source& as, seastar::noncopyable_function<future<>()> func);
|
||||
|
||||
future<> create_metadata_table_if_missing(
|
||||
stdx::string_view table_name,
|
||||
const sstring& table_name,
|
||||
cql3::query_processor&,
|
||||
stdx::string_view cql,
|
||||
const sstring& cql,
|
||||
::service::migration_manager&);
|
||||
|
||||
future<> wait_for_schema_agreement(::service::migration_manager&, const database&);
|
||||
|
||||
}
|
||||
|
||||
171
auth/data_resource.cc
Normal file
171
auth/data_resource.cc
Normal file
@@ -0,0 +1,171 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Copyright (C) 2016 ScyllaDB
|
||||
*
|
||||
* Modified by ScyllaDB
|
||||
*/
|
||||
|
||||
/*
|
||||
* This file is part of Scylla.
|
||||
*
|
||||
* Scylla is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Scylla is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Scylla. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "data_resource.hh"
|
||||
|
||||
#include <regex>
|
||||
#include "service/storage_proxy.hh"
|
||||
|
||||
const sstring auth::data_resource::ROOT_NAME("data");
|
||||
|
||||
auth::data_resource::data_resource(level l, const sstring& ks, const sstring& cf)
|
||||
: _level(l), _ks(ks), _cf(cf)
|
||||
{
|
||||
}
|
||||
|
||||
auth::data_resource::data_resource()
|
||||
: data_resource(level::ROOT)
|
||||
{}
|
||||
|
||||
auth::data_resource::data_resource(const sstring& ks)
|
||||
: data_resource(level::KEYSPACE, ks)
|
||||
{}
|
||||
|
||||
auth::data_resource::data_resource(const sstring& ks, const sstring& cf)
|
||||
: data_resource(level::COLUMN_FAMILY, ks, cf)
|
||||
{}
|
||||
|
||||
auth::data_resource::level auth::data_resource::get_level() const {
|
||||
return _level;
|
||||
}
|
||||
|
||||
auth::data_resource auth::data_resource::from_name(
|
||||
const sstring& s) {
|
||||
|
||||
static std::regex slash_regex("/");
|
||||
|
||||
auto i = std::regex_token_iterator<sstring::const_iterator>(s.begin(),
|
||||
s.end(), slash_regex, -1);
|
||||
auto e = std::regex_token_iterator<sstring::const_iterator>();
|
||||
auto n = std::distance(i, e);
|
||||
|
||||
if (n > 3 || ROOT_NAME != sstring(*i++)) {
|
||||
throw std::invalid_argument(sprint("%s is not a valid data resource name", s));
|
||||
}
|
||||
|
||||
if (n == 1) {
|
||||
return data_resource();
|
||||
}
|
||||
auto ks = *i++;
|
||||
if (n == 2) {
|
||||
return data_resource(ks.str());
|
||||
}
|
||||
auto cf = *i++;
|
||||
return data_resource(ks.str(), cf.str());
|
||||
}
|
||||
|
||||
sstring auth::data_resource::name() const {
|
||||
switch (get_level()) {
|
||||
case level::ROOT:
|
||||
return ROOT_NAME;
|
||||
case level::KEYSPACE:
|
||||
return sprint("%s/%s", ROOT_NAME, _ks);
|
||||
case level::COLUMN_FAMILY:
|
||||
default:
|
||||
return sprint("%s/%s/%s", ROOT_NAME, _ks, _cf);
|
||||
}
|
||||
}
|
||||
|
||||
auth::data_resource auth::data_resource::get_parent() const {
|
||||
switch (get_level()) {
|
||||
case level::KEYSPACE:
|
||||
return data_resource();
|
||||
case level::COLUMN_FAMILY:
|
||||
return data_resource(_ks);
|
||||
default:
|
||||
throw std::invalid_argument("Root-level resource can't have a parent");
|
||||
}
|
||||
}
|
||||
|
||||
const sstring& auth::data_resource::keyspace() const {
|
||||
if (is_root_level()) {
|
||||
throw std::invalid_argument("ROOT data resource has no keyspace");
|
||||
}
|
||||
return _ks;
|
||||
}
|
||||
|
||||
const sstring& auth::data_resource::column_family() const {
|
||||
if (!is_column_family_level()) {
|
||||
throw std::invalid_argument(sprint("%s data resource has no column family", name()));
|
||||
}
|
||||
return _cf;
|
||||
}
|
||||
|
||||
bool auth::data_resource::has_parent() const {
|
||||
return !is_root_level();
|
||||
}
|
||||
|
||||
bool auth::data_resource::exists() const {
|
||||
switch (get_level()) {
|
||||
case level::ROOT:
|
||||
return true;
|
||||
case level::KEYSPACE:
|
||||
return service::get_local_storage_proxy().get_db().local().has_keyspace(_ks);
|
||||
case level::COLUMN_FAMILY:
|
||||
default:
|
||||
return service::get_local_storage_proxy().get_db().local().has_schema(_ks, _cf);
|
||||
}
|
||||
}
|
||||
|
||||
sstring auth::data_resource::to_string() const {
|
||||
switch (get_level()) {
|
||||
case level::ROOT:
|
||||
return "<all keyspaces>";
|
||||
case level::KEYSPACE:
|
||||
return sprint("<keyspace %s>", _ks);
|
||||
case level::COLUMN_FAMILY:
|
||||
default:
|
||||
return sprint("<table %s.%s>", _ks, _cf);
|
||||
}
|
||||
}
|
||||
|
||||
bool auth::data_resource::operator==(const data_resource& v) const {
|
||||
return _ks == v._ks && _cf == v._cf;
|
||||
}
|
||||
|
||||
bool auth::data_resource::operator<(const data_resource& v) const {
|
||||
return _ks < v._ks ? true : (v._ks < _ks ? false : _cf < v._cf);
|
||||
}
|
||||
|
||||
std::ostream& auth::operator<<(std::ostream& os, const data_resource& r) {
|
||||
return os << r.to_string();
|
||||
}
|
||||
|
||||
159
auth/data_resource.hh
Normal file
159
auth/data_resource.hh
Normal file
@@ -0,0 +1,159 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Copyright (C) 2016 ScyllaDB
|
||||
*
|
||||
* Modified by ScyllaDB
|
||||
*/
|
||||
|
||||
/*
|
||||
* This file is part of Scylla.
|
||||
*
|
||||
* Scylla is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Scylla is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Scylla. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "utils/hash.hh"
|
||||
#include <iosfwd>
|
||||
#include <set>
|
||||
#include <seastar/core/sstring.hh>
|
||||
#include "seastarx.hh"
|
||||
|
||||
namespace auth {
|
||||
|
||||
class data_resource {
|
||||
private:
|
||||
enum class level {
|
||||
ROOT, KEYSPACE, COLUMN_FAMILY
|
||||
};
|
||||
|
||||
static const sstring ROOT_NAME;
|
||||
|
||||
level _level;
|
||||
sstring _ks;
|
||||
sstring _cf;
|
||||
|
||||
data_resource(level, const sstring& ks = {}, const sstring& cf = {});
|
||||
|
||||
level get_level() const;
|
||||
public:
|
||||
/**
|
||||
* Creates a DataResource representing the root-level resource.
|
||||
* @return the root-level resource.
|
||||
*/
|
||||
data_resource();
|
||||
/**
|
||||
* Creates a DataResource representing a keyspace.
|
||||
*
|
||||
* @param keyspace Name of the keyspace.
|
||||
*/
|
||||
data_resource(const sstring& ks);
|
||||
/**
|
||||
* Creates a DataResource instance representing a column family.
|
||||
*
|
||||
* @param keyspace Name of the keyspace.
|
||||
* @param columnFamily Name of the column family.
|
||||
*/
|
||||
data_resource(const sstring& ks, const sstring& cf);
|
||||
|
||||
/**
|
||||
* Parses a data resource name into a DataResource instance.
|
||||
*
|
||||
* @param name Name of the data resource.
|
||||
* @return DataResource instance matching the name.
|
||||
*/
|
||||
static data_resource from_name(const sstring&);
|
||||
|
||||
/**
|
||||
* @return Printable name of the resource.
|
||||
*/
|
||||
sstring name() const;
|
||||
|
||||
/**
|
||||
* @return Parent of the resource, if any. Throws IllegalStateException if it's the root-level resource.
|
||||
*/
|
||||
data_resource get_parent() const;
|
||||
|
||||
bool is_root_level() const {
|
||||
return get_level() == level::ROOT;
|
||||
}
|
||||
|
||||
bool is_keyspace_level() const {
|
||||
return get_level() == level::KEYSPACE;
|
||||
}
|
||||
|
||||
bool is_column_family_level() const {
|
||||
return get_level() == level::COLUMN_FAMILY;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return keyspace of the resource.
|
||||
* @throws std::invalid_argument if it's the root-level resource.
|
||||
*/
|
||||
const sstring& keyspace() const;
|
||||
|
||||
/**
|
||||
* @return column family of the resource.
|
||||
* @throws std::invalid_argument if it's not a cf-level resource.
|
||||
*/
|
||||
const sstring& column_family() const;
|
||||
|
||||
/**
|
||||
* @return Whether or not the resource has a parent in the hierarchy.
|
||||
*/
|
||||
bool has_parent() const;
|
||||
|
||||
/**
|
||||
* @return Whether or not the resource exists in scylla.
|
||||
*/
|
||||
bool exists() const;
|
||||
|
||||
sstring to_string() const;
|
||||
|
||||
bool operator==(const data_resource&) const;
|
||||
bool operator<(const data_resource&) const;
|
||||
|
||||
size_t hash_value() const {
|
||||
return utils::tuple_hash()(_ks, _cf);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Resource id mappings, i.e. keyspace and/or column families.
|
||||
*/
|
||||
using resource_ids = std::set<data_resource>;
|
||||
|
||||
std::ostream& operator<<(std::ostream&, const data_resource&);
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -39,283 +39,198 @@
|
||||
* along with Scylla. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "auth/default_authorizer.hh"
|
||||
|
||||
extern "C" {
|
||||
#include <crypt.h>
|
||||
#include <unistd.h>
|
||||
}
|
||||
|
||||
#include <chrono>
|
||||
#include <crypt.h>
|
||||
#include <random>
|
||||
#include <chrono>
|
||||
|
||||
#include <boost/algorithm/string/join.hpp>
|
||||
#include <boost/range.hpp>
|
||||
#include <seastar/core/reactor.hh>
|
||||
|
||||
#include "auth/authenticated_user.hh"
|
||||
#include "auth/common.hh"
|
||||
#include "auth/permission.hh"
|
||||
#include "auth/role_or_anonymous.hh"
|
||||
#include "common.hh"
|
||||
#include "default_authorizer.hh"
|
||||
#include "authenticated_user.hh"
|
||||
#include "permission.hh"
|
||||
#include "cql3/query_processor.hh"
|
||||
#include "cql3/untyped_result_set.hh"
|
||||
#include "exceptions/exceptions.hh"
|
||||
#include "log.hh"
|
||||
|
||||
namespace auth {
|
||||
|
||||
const sstring& default_authorizer_name() {
|
||||
const sstring& auth::default_authorizer_name() {
|
||||
static const sstring name = meta::AUTH_PACKAGE_NAME + "CassandraAuthorizer";
|
||||
return name;
|
||||
}
|
||||
|
||||
static const sstring ROLE_NAME = "role";
|
||||
static const sstring USER_NAME = "username";
|
||||
static const sstring RESOURCE_NAME = "resource";
|
||||
static const sstring PERMISSIONS_NAME = "permissions";
|
||||
static const sstring PERMISSIONS_CF = "role_permissions";
|
||||
static const sstring PERMISSIONS_CF = "permissions";
|
||||
|
||||
static logging::logger alogger("default_authorizer");
|
||||
|
||||
// To ensure correct initialization order, we unfortunately need to use a string literal.
|
||||
static const class_registrator<
|
||||
authorizer,
|
||||
default_authorizer,
|
||||
auth::authorizer,
|
||||
auth::default_authorizer,
|
||||
cql3::query_processor&,
|
||||
::service::migration_manager&> password_auth_reg("org.apache.cassandra.auth.CassandraAuthorizer");
|
||||
|
||||
default_authorizer::default_authorizer(cql3::query_processor& qp, ::service::migration_manager& mm)
|
||||
auth::default_authorizer::default_authorizer(cql3::query_processor& qp, ::service::migration_manager& mm)
|
||||
: _qp(qp)
|
||||
, _migration_manager(mm) {
|
||||
}
|
||||
|
||||
default_authorizer::~default_authorizer() {
|
||||
auth::default_authorizer::~default_authorizer() {
|
||||
}
|
||||
|
||||
static const sstring legacy_table_name{"permissions"};
|
||||
future<> auth::default_authorizer::start() {
|
||||
static const sstring create_table = sprint("CREATE TABLE %s.%s ("
|
||||
"%s text,"
|
||||
"%s text,"
|
||||
"%s set<text>,"
|
||||
"PRIMARY KEY(%s, %s)"
|
||||
") WITH gc_grace_seconds=%d", meta::AUTH_KS,
|
||||
PERMISSIONS_CF, USER_NAME, RESOURCE_NAME, PERMISSIONS_NAME,
|
||||
USER_NAME, RESOURCE_NAME, 90 * 24 * 60 * 60); // 3 months.
|
||||
|
||||
bool default_authorizer::legacy_metadata_exists() const {
|
||||
return _qp.db().local().has_schema(meta::AUTH_KS, legacy_table_name);
|
||||
}
|
||||
|
||||
future<bool> default_authorizer::any_granted() const {
|
||||
static const sstring query = sprint("SELECT * FROM %s.%s LIMIT 1", meta::AUTH_KS, PERMISSIONS_CF);
|
||||
|
||||
return _qp.process(
|
||||
query,
|
||||
db::consistency_level::LOCAL_ONE,
|
||||
{},
|
||||
true).then([this](::shared_ptr<cql3::untyped_result_set> results) {
|
||||
return !results->empty();
|
||||
});
|
||||
}
|
||||
|
||||
future<> default_authorizer::migrate_legacy_metadata() const {
|
||||
alogger.info("Starting migration of legacy permissions metadata.");
|
||||
static const sstring query = sprint("SELECT * FROM %s.%s", meta::AUTH_KS, legacy_table_name);
|
||||
|
||||
return _qp.process(
|
||||
query,
|
||||
db::consistency_level::LOCAL_ONE).then([this](::shared_ptr<cql3::untyped_result_set> results) {
|
||||
return do_for_each(*results, [this](const cql3::untyped_result_set_row& row) {
|
||||
return do_with(
|
||||
row.get_as<sstring>("username"),
|
||||
parse_resource(row.get_as<sstring>(RESOURCE_NAME)),
|
||||
[this, &row](const auto& username, const auto& r) {
|
||||
const permission_set perms = permissions::from_strings(row.get_set<sstring>(PERMISSIONS_NAME));
|
||||
return grant(username, perms, r);
|
||||
});
|
||||
}).finally([results] {});
|
||||
}).then([] {
|
||||
alogger.info("Finished migrating legacy permissions metadata.");
|
||||
}).handle_exception([](std::exception_ptr ep) {
|
||||
alogger.error("Encountered an error during migration!");
|
||||
std::rethrow_exception(ep);
|
||||
});
|
||||
}
|
||||
|
||||
future<> default_authorizer::start() {
|
||||
static const sstring create_table = sprint(
|
||||
"CREATE TABLE %s.%s ("
|
||||
"%s text,"
|
||||
"%s text,"
|
||||
"%s set<text>,"
|
||||
"PRIMARY KEY(%s, %s)"
|
||||
") WITH gc_grace_seconds=%d",
|
||||
meta::AUTH_KS,
|
||||
PERMISSIONS_CF,
|
||||
ROLE_NAME,
|
||||
RESOURCE_NAME,
|
||||
PERMISSIONS_NAME,
|
||||
ROLE_NAME,
|
||||
RESOURCE_NAME,
|
||||
90 * 24 * 60 * 60); // 3 months.
|
||||
|
||||
return once_among_shards([this] {
|
||||
return create_metadata_table_if_missing(
|
||||
return auth::once_among_shards([this] {
|
||||
return auth::create_metadata_table_if_missing(
|
||||
PERMISSIONS_CF,
|
||||
_qp,
|
||||
create_table,
|
||||
_migration_manager).then([this] {
|
||||
_finished = do_after_system_ready(_as, [this] {
|
||||
return async([this] {
|
||||
wait_for_schema_agreement(_migration_manager, _qp.db().local()).get0();
|
||||
_migration_manager);
|
||||
});
|
||||
}
|
||||
|
||||
if (legacy_metadata_exists()) {
|
||||
if (!any_granted().get0()) {
|
||||
migrate_legacy_metadata().get0();
|
||||
return;
|
||||
}
|
||||
future<> auth::default_authorizer::stop() {
|
||||
return make_ready_future<>();
|
||||
}
|
||||
|
||||
alogger.warn("Ignoring legacy permissions metadata since role permissions exist.");
|
||||
}
|
||||
});
|
||||
});
|
||||
future<auth::permission_set> auth::default_authorizer::authorize(
|
||||
service& ser, ::shared_ptr<authenticated_user> user, data_resource resource) const {
|
||||
return auth::is_super_user(ser, *user).then([this, user, resource = std::move(resource)](bool is_super) {
|
||||
if (is_super) {
|
||||
return make_ready_future<permission_set>(permissions::ALL);
|
||||
}
|
||||
|
||||
/**
|
||||
* TOOD: could create actual data type for permission (translating string<->perm),
|
||||
* but this seems overkill right now. We still must store strings so...
|
||||
*/
|
||||
auto query = sprint("SELECT %s FROM %s.%s WHERE %s = ? AND %s = ?"
|
||||
, PERMISSIONS_NAME, meta::AUTH_KS, PERMISSIONS_CF, USER_NAME, RESOURCE_NAME);
|
||||
return _qp.process(query, db::consistency_level::LOCAL_ONE, {user->name(), resource.name() })
|
||||
.then_wrapped([=](future<::shared_ptr<cql3::untyped_result_set>> f) {
|
||||
try {
|
||||
auto res = f.get0();
|
||||
|
||||
if (res->empty() || !res->one().has(PERMISSIONS_NAME)) {
|
||||
return make_ready_future<permission_set>(permissions::NONE);
|
||||
}
|
||||
return make_ready_future<permission_set>(permissions::from_strings(res->one().get_set<sstring>(PERMISSIONS_NAME)));
|
||||
} catch (exceptions::request_execution_exception& e) {
|
||||
alogger.warn("CassandraAuthorizer failed to authorize {} for {}", user->name(), resource);
|
||||
return make_ready_future<permission_set>(permissions::NONE);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
future<> default_authorizer::stop() {
|
||||
_as.request_abort();
|
||||
return _finished.handle_exception_type([](const sleep_aborted&) {});
|
||||
#include <boost/range.hpp>
|
||||
|
||||
future<> auth::default_authorizer::modify(
|
||||
::shared_ptr<authenticated_user> performer, permission_set set,
|
||||
data_resource resource, sstring user, sstring op) {
|
||||
// TODO: why does this not check super user?
|
||||
auto query = sprint("UPDATE %s.%s SET %s = %s %s ? WHERE %s = ? AND %s = ?",
|
||||
meta::AUTH_KS, PERMISSIONS_CF, PERMISSIONS_NAME,
|
||||
PERMISSIONS_NAME, op, USER_NAME, RESOURCE_NAME);
|
||||
return _qp.process(query, db::consistency_level::ONE, {
|
||||
permissions::to_strings(set), user, resource.name() }).discard_result();
|
||||
}
|
||||
|
||||
future<permission_set>
|
||||
default_authorizer::authorize(const role_or_anonymous& maybe_role, const resource& r) const {
|
||||
if (is_anonymous(maybe_role)) {
|
||||
return make_ready_future<permission_set>(permissions::NONE);
|
||||
}
|
||||
|
||||
static const sstring query = sprint(
|
||||
"SELECT %s FROM %s.%s WHERE %s = ? AND %s = ?",
|
||||
PERMISSIONS_NAME,
|
||||
meta::AUTH_KS,
|
||||
PERMISSIONS_CF,
|
||||
ROLE_NAME,
|
||||
RESOURCE_NAME);
|
||||
future<> auth::default_authorizer::grant(
|
||||
::shared_ptr<authenticated_user> performer, permission_set set,
|
||||
data_resource resource, sstring to) {
|
||||
return modify(std::move(performer), std::move(set), std::move(resource), std::move(to), "+");
|
||||
}
|
||||
|
||||
return _qp.process(
|
||||
query,
|
||||
db::consistency_level::LOCAL_ONE,
|
||||
{*maybe_role.name, r.name()}).then([](::shared_ptr<cql3::untyped_result_set> results) {
|
||||
if (results->empty()) {
|
||||
return permissions::NONE;
|
||||
future<> auth::default_authorizer::revoke(
|
||||
::shared_ptr<authenticated_user> performer, permission_set set,
|
||||
data_resource resource, sstring from) {
|
||||
return modify(std::move(performer), std::move(set), std::move(resource), std::move(from), "-");
|
||||
}
|
||||
|
||||
future<std::vector<auth::permission_details>> auth::default_authorizer::list(
|
||||
service& ser, ::shared_ptr<authenticated_user> performer, permission_set set,
|
||||
optional<data_resource> resource, optional<sstring> user) const {
|
||||
return auth::is_super_user(ser, *performer).then([this, performer, set = std::move(set), resource = std::move(resource), user = std::move(user)](bool is_super) {
|
||||
if (!is_super && (!user || performer->name() != *user)) {
|
||||
throw exceptions::unauthorized_exception(sprint("You are not authorized to view %s's permissions", user ? *user : "everyone"));
|
||||
}
|
||||
|
||||
return permissions::from_strings(results->one().get_set<sstring>(PERMISSIONS_NAME));
|
||||
});
|
||||
}
|
||||
auto query = sprint("SELECT %s, %s, %s FROM %s.%s", USER_NAME, RESOURCE_NAME, PERMISSIONS_NAME, meta::AUTH_KS, PERMISSIONS_CF);
|
||||
|
||||
future<>
|
||||
default_authorizer::modify(
|
||||
stdx::string_view role_name,
|
||||
permission_set set,
|
||||
const resource& resource,
|
||||
stdx::string_view op) const {
|
||||
return do_with(
|
||||
sprint(
|
||||
"UPDATE %s.%s SET %s = %s %s ? WHERE %s = ? AND %s = ?",
|
||||
meta::AUTH_KS,
|
||||
PERMISSIONS_CF,
|
||||
PERMISSIONS_NAME,
|
||||
PERMISSIONS_NAME,
|
||||
op,
|
||||
ROLE_NAME,
|
||||
RESOURCE_NAME),
|
||||
[this, &role_name, set, &resource](const auto& query) {
|
||||
return _qp.process(
|
||||
query,
|
||||
db::consistency_level::ONE,
|
||||
{permissions::to_strings(set), sstring(role_name), resource.name()}).discard_result();
|
||||
});
|
||||
}
|
||||
// Oh, look, it is a case where it does not pay off to have
|
||||
// parameters to process in an initializer list.
|
||||
future<::shared_ptr<cql3::untyped_result_set>> f = make_ready_future<::shared_ptr<cql3::untyped_result_set>>();
|
||||
|
||||
if (resource && user) {
|
||||
query += sprint(" WHERE %s = ? AND %s = ?", USER_NAME, RESOURCE_NAME);
|
||||
f = _qp.process(query, db::consistency_level::ONE, {*user, resource->name()});
|
||||
} else if (resource) {
|
||||
query += sprint(" WHERE %s = ? ALLOW FILTERING", RESOURCE_NAME);
|
||||
f = _qp.process(query, db::consistency_level::ONE, {resource->name()});
|
||||
} else if (user) {
|
||||
query += sprint(" WHERE %s = ?", USER_NAME);
|
||||
f = _qp.process(query, db::consistency_level::ONE, {*user});
|
||||
} else {
|
||||
f = _qp.process(query, db::consistency_level::ONE, {});
|
||||
}
|
||||
|
||||
future<> default_authorizer::grant(stdx::string_view role_name, permission_set set, const resource& resource) const {
|
||||
return modify(role_name, std::move(set), resource, "+");
|
||||
}
|
||||
return f.then([set](::shared_ptr<cql3::untyped_result_set> res) {
|
||||
std::vector<permission_details> result;
|
||||
|
||||
future<> default_authorizer::revoke(stdx::string_view role_name, permission_set set, const resource& resource) const {
|
||||
return modify(role_name, std::move(set), resource, "-");
|
||||
}
|
||||
for (auto& row : *res) {
|
||||
if (row.has(PERMISSIONS_NAME)) {
|
||||
auto username = row.get_as<sstring>(USER_NAME);
|
||||
auto resource = data_resource::from_name(row.get_as<sstring>(RESOURCE_NAME));
|
||||
auto ps = permissions::from_strings(row.get_set<sstring>(PERMISSIONS_NAME));
|
||||
ps = permission_set::from_mask(ps.mask() & set.mask());
|
||||
|
||||
future<std::vector<permission_details>> default_authorizer::list_all() const {
|
||||
static const sstring query = sprint(
|
||||
"SELECT %s, %s, %s FROM %s.%s",
|
||||
ROLE_NAME,
|
||||
RESOURCE_NAME,
|
||||
PERMISSIONS_NAME,
|
||||
meta::AUTH_KS,
|
||||
PERMISSIONS_CF);
|
||||
|
||||
return _qp.process(
|
||||
query,
|
||||
db::consistency_level::ONE,
|
||||
{},
|
||||
true).then([](::shared_ptr<cql3::untyped_result_set> results) {
|
||||
std::vector<permission_details> all_details;
|
||||
|
||||
for (const auto& row : *results) {
|
||||
if (row.has(PERMISSIONS_NAME)) {
|
||||
auto role_name = row.get_as<sstring>(ROLE_NAME);
|
||||
auto resource = parse_resource(row.get_as<sstring>(RESOURCE_NAME));
|
||||
auto perms = permissions::from_strings(row.get_set<sstring>(PERMISSIONS_NAME));
|
||||
all_details.push_back(permission_details{std::move(role_name), std::move(resource), std::move(perms)});
|
||||
result.emplace_back(permission_details {username, resource, ps});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return all_details;
|
||||
return make_ready_future<std::vector<permission_details>>(std::move(result));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
future<> default_authorizer::revoke_all(stdx::string_view role_name) const {
|
||||
static const sstring query = sprint(
|
||||
"DELETE FROM %s.%s WHERE %s = ?",
|
||||
meta::AUTH_KS,
|
||||
PERMISSIONS_CF,
|
||||
ROLE_NAME);
|
||||
|
||||
return _qp.process(
|
||||
query,
|
||||
db::consistency_level::ONE,
|
||||
{sstring(role_name)}).discard_result().handle_exception([role_name](auto ep) {
|
||||
try {
|
||||
std::rethrow_exception(ep);
|
||||
} catch (exceptions::request_execution_exception& e) {
|
||||
alogger.warn("CassandraAuthorizer failed to revoke all permissions of {}: {}", role_name, e);
|
||||
}
|
||||
});
|
||||
future<> auth::default_authorizer::revoke_all(sstring dropped_user) {
|
||||
auto query = sprint("DELETE FROM %s.%s WHERE %s = ?", meta::AUTH_KS,
|
||||
PERMISSIONS_CF, USER_NAME);
|
||||
return _qp.process(query, db::consistency_level::ONE, { dropped_user }).discard_result().handle_exception(
|
||||
[dropped_user](auto ep) {
|
||||
try {
|
||||
std::rethrow_exception(ep);
|
||||
} catch (exceptions::request_execution_exception& e) {
|
||||
alogger.warn("CassandraAuthorizer failed to revoke all permissions of {}: {}", dropped_user, e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
future<> default_authorizer::revoke_all(const resource& resource) const {
|
||||
static const sstring query = sprint(
|
||||
"SELECT %s FROM %s.%s WHERE %s = ? ALLOW FILTERING",
|
||||
ROLE_NAME,
|
||||
meta::AUTH_KS,
|
||||
PERMISSIONS_CF,
|
||||
RESOURCE_NAME);
|
||||
|
||||
return _qp.process(
|
||||
query,
|
||||
db::consistency_level::LOCAL_ONE,
|
||||
{resource.name()}).then_wrapped([this, resource](future<::shared_ptr<cql3::untyped_result_set>> f) {
|
||||
future<> auth::default_authorizer::revoke_all(data_resource resource) {
|
||||
auto query = sprint("SELECT %s FROM %s.%s WHERE %s = ? ALLOW FILTERING",
|
||||
USER_NAME, meta::AUTH_KS, PERMISSIONS_CF, RESOURCE_NAME);
|
||||
return _qp.process(query, db::consistency_level::LOCAL_ONE, { resource.name() })
|
||||
.then_wrapped([this, resource](future<::shared_ptr<cql3::untyped_result_set>> f) {
|
||||
try {
|
||||
auto res = f.get0();
|
||||
return parallel_for_each(
|
||||
res->begin(),
|
||||
res->end(),
|
||||
[this, res, resource](const cql3::untyped_result_set::row& r) {
|
||||
static const sstring query = sprint(
|
||||
"DELETE FROM %s.%s WHERE %s = ? AND %s = ?",
|
||||
meta::AUTH_KS,
|
||||
PERMISSIONS_CF,
|
||||
ROLE_NAME,
|
||||
RESOURCE_NAME);
|
||||
|
||||
return _qp.process(
|
||||
query,
|
||||
db::consistency_level::LOCAL_ONE,
|
||||
{r.get_as<sstring>(ROLE_NAME), resource.name()}).discard_result().handle_exception(
|
||||
[resource](auto ep) {
|
||||
return parallel_for_each(res->begin(), res->end(), [this, res, resource](const cql3::untyped_result_set::row& r) {
|
||||
auto query = sprint("DELETE FROM %s.%s WHERE %s = ? AND %s = ?"
|
||||
, meta::AUTH_KS, PERMISSIONS_CF, USER_NAME, RESOURCE_NAME);
|
||||
return _qp.process(query, db::consistency_level::LOCAL_ONE, { r.get_as<sstring>(USER_NAME), resource.name() })
|
||||
.discard_result().handle_exception([resource](auto ep) {
|
||||
try {
|
||||
std::rethrow_exception(ep);
|
||||
} catch (exceptions::request_execution_exception& e) {
|
||||
@@ -331,9 +246,12 @@ future<> default_authorizer::revoke_all(const resource& resource) const {
|
||||
});
|
||||
}
|
||||
|
||||
const resource_set& default_authorizer::protected_resources() const {
|
||||
static const resource_set resources({ make_data_resource(meta::AUTH_KS, PERMISSIONS_CF) });
|
||||
return resources;
|
||||
|
||||
const auth::resource_ids& auth::default_authorizer::protected_resources() {
|
||||
static const resource_ids ids({ data_resource(meta::AUTH_KS, PERMISSIONS_CF) });
|
||||
return ids;
|
||||
}
|
||||
|
||||
future<> auth::default_authorizer::validate_configuration() const {
|
||||
return make_ready_future();
|
||||
}
|
||||
|
||||
@@ -43,9 +43,7 @@
|
||||
|
||||
#include <functional>
|
||||
|
||||
#include <seastar/core/abort_source.hh>
|
||||
|
||||
#include "auth/authorizer.hh"
|
||||
#include "authorizer.hh"
|
||||
#include "cql3/query_processor.hh"
|
||||
#include "service/migration_manager.hh"
|
||||
|
||||
@@ -58,45 +56,36 @@ class default_authorizer : public authorizer {
|
||||
|
||||
::service::migration_manager& _migration_manager;
|
||||
|
||||
abort_source _as{};
|
||||
|
||||
future<> _finished{make_ready_future<>()};
|
||||
|
||||
public:
|
||||
default_authorizer(cql3::query_processor&, ::service::migration_manager&);
|
||||
|
||||
~default_authorizer();
|
||||
|
||||
virtual future<> start() override;
|
||||
future<> start() override;
|
||||
|
||||
virtual future<> stop() override;
|
||||
future<> stop() override;
|
||||
|
||||
virtual const sstring& qualified_java_name() const override {
|
||||
const sstring& qualified_java_name() const override {
|
||||
return default_authorizer_name();
|
||||
}
|
||||
|
||||
virtual future<permission_set> authorize(const role_or_anonymous&, const resource&) const override;
|
||||
future<permission_set> authorize(service&, ::shared_ptr<authenticated_user>, data_resource) const override;
|
||||
|
||||
virtual future<> grant(stdx::string_view, permission_set, const resource&) const override;
|
||||
future<> grant(::shared_ptr<authenticated_user>, permission_set, data_resource, sstring) override;
|
||||
|
||||
virtual future<> revoke( stdx::string_view, permission_set, const resource&) const override;
|
||||
future<> revoke(::shared_ptr<authenticated_user>, permission_set, data_resource, sstring) override;
|
||||
|
||||
virtual future<std::vector<permission_details>> list_all() const override;
|
||||
future<std::vector<permission_details>> list(service&, ::shared_ptr<authenticated_user>, permission_set, optional<data_resource>, optional<sstring>) const override;
|
||||
|
||||
virtual future<> revoke_all(stdx::string_view) const override;
|
||||
future<> revoke_all(sstring) override;
|
||||
|
||||
virtual future<> revoke_all(const resource&) const override;
|
||||
future<> revoke_all(data_resource) override;
|
||||
|
||||
virtual const resource_set& protected_resources() const override;
|
||||
const resource_ids& protected_resources() override;
|
||||
|
||||
future<> validate_configuration() const override;
|
||||
|
||||
private:
|
||||
bool legacy_metadata_exists() const;
|
||||
|
||||
future<bool> any_granted() const;
|
||||
|
||||
future<> migrate_legacy_metadata() const;
|
||||
|
||||
future<> modify(stdx::string_view, permission_set, const resource&, stdx::string_view) const;
|
||||
future<> modify(::shared_ptr<authenticated_user>, permission_set, data_resource, sstring, sstring);
|
||||
};
|
||||
|
||||
} /* namespace auth */
|
||||
|
||||
@@ -39,56 +39,48 @@
|
||||
* along with Scylla. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "auth/password_authenticator.hh"
|
||||
|
||||
extern "C" {
|
||||
#include <crypt.h>
|
||||
#include <unistd.h>
|
||||
}
|
||||
|
||||
#include <algorithm>
|
||||
#include <chrono>
|
||||
#include <crypt.h>
|
||||
#include <random>
|
||||
#include <chrono>
|
||||
|
||||
#include <boost/algorithm/cxx11/all_of.hpp>
|
||||
#include <seastar/core/reactor.hh>
|
||||
|
||||
#include "auth/authenticated_user.hh"
|
||||
#include "auth/common.hh"
|
||||
#include "auth/roles-metadata.hh"
|
||||
#include "common.hh"
|
||||
#include "password_authenticator.hh"
|
||||
#include "authenticated_user.hh"
|
||||
#include "cql3/untyped_result_set.hh"
|
||||
#include "log.hh"
|
||||
#include "service/migration_manager.hh"
|
||||
#include "utils/class_registrator.hh"
|
||||
|
||||
namespace auth {
|
||||
|
||||
const sstring& password_authenticator_name() {
|
||||
const sstring& auth::password_authenticator_name() {
|
||||
static const sstring name = meta::AUTH_PACKAGE_NAME + "PasswordAuthenticator";
|
||||
return name;
|
||||
}
|
||||
|
||||
// name of the hash column.
|
||||
static const sstring SALTED_HASH = "salted_hash";
|
||||
static const sstring DEFAULT_USER_NAME = meta::DEFAULT_SUPERUSER_NAME;
|
||||
static const sstring DEFAULT_USER_PASSWORD = meta::DEFAULT_SUPERUSER_NAME;
|
||||
static const sstring USER_NAME = "username";
|
||||
static const sstring DEFAULT_USER_NAME = auth::meta::DEFAULT_SUPERUSER_NAME;
|
||||
static const sstring DEFAULT_USER_PASSWORD = auth::meta::DEFAULT_SUPERUSER_NAME;
|
||||
static const sstring CREDENTIALS_CF = "credentials";
|
||||
|
||||
static logging::logger plogger("password_authenticator");
|
||||
|
||||
// To ensure correct initialization order, we unfortunately need to use a string literal.
|
||||
static const class_registrator<
|
||||
authenticator,
|
||||
password_authenticator,
|
||||
auth::authenticator,
|
||||
auth::password_authenticator,
|
||||
cql3::query_processor&,
|
||||
::service::migration_manager&> password_auth_reg("org.apache.cassandra.auth.PasswordAuthenticator");
|
||||
|
||||
password_authenticator::~password_authenticator() {
|
||||
}
|
||||
auth::password_authenticator::~password_authenticator()
|
||||
{}
|
||||
|
||||
password_authenticator::password_authenticator(cql3::query_processor& qp, ::service::migration_manager& mm)
|
||||
auth::password_authenticator::password_authenticator(cql3::query_processor& qp, ::service::migration_manager& mm)
|
||||
: _qp(qp)
|
||||
, _migration_manager(mm)
|
||||
, _stopped(make_ready_future<>()) {
|
||||
, _migration_manager(mm) {
|
||||
}
|
||||
|
||||
// TODO: blowfish
|
||||
@@ -163,125 +155,76 @@ static sstring hashpw(const sstring& pass) {
|
||||
return hashpw(pass, gensalt());
|
||||
}
|
||||
|
||||
static bool has_salted_hash(const cql3::untyped_result_set_row& row) {
|
||||
return utf8_type->deserialize(row.get_blob(SALTED_HASH)) != data_value::make_null(utf8_type);
|
||||
}
|
||||
future<> auth::password_authenticator::start() {
|
||||
return auth::once_among_shards([this] {
|
||||
gensalt(); // do this once to determine usable hashing
|
||||
|
||||
static const sstring update_row_query = sprint(
|
||||
"UPDATE %s SET %s = ? WHERE %s = ?",
|
||||
meta::roles_table::qualified_name(),
|
||||
SALTED_HASH,
|
||||
meta::roles_table::role_col_name);
|
||||
static const sstring create_table = sprint(
|
||||
"CREATE TABLE %s.%s ("
|
||||
"%s text,"
|
||||
"%s text," // salt + hash + number of rounds
|
||||
"options map<text,text>,"// for future extensions
|
||||
"PRIMARY KEY(%s)"
|
||||
") WITH gc_grace_seconds=%d",
|
||||
meta::AUTH_KS,
|
||||
CREDENTIALS_CF, USER_NAME, SALTED_HASH, USER_NAME,
|
||||
90 * 24 * 60 * 60); // 3 months.
|
||||
|
||||
static const sstring legacy_table_name{"credentials"};
|
||||
return auth::create_metadata_table_if_missing(
|
||||
CREDENTIALS_CF,
|
||||
_qp,
|
||||
create_table,
|
||||
_migration_manager).then([this] {
|
||||
auth::delay_until_system_ready(_delayed, [this] {
|
||||
return has_existing_users().then([this](bool existing) {
|
||||
if (!existing) {
|
||||
return _qp.process(
|
||||
sprint(
|
||||
"INSERT INTO %s.%s (%s, %s) VALUES (?, ?) USING TIMESTAMP 0",
|
||||
meta::AUTH_KS,
|
||||
CREDENTIALS_CF,
|
||||
USER_NAME, SALTED_HASH),
|
||||
db::consistency_level::ONE,
|
||||
{ DEFAULT_USER_NAME, hashpw(DEFAULT_USER_PASSWORD) }).then([](auto) {
|
||||
plogger.info("Created default user '{}'", DEFAULT_USER_NAME);
|
||||
});
|
||||
}
|
||||
|
||||
bool password_authenticator::legacy_metadata_exists() const {
|
||||
return _qp.db().local().has_schema(meta::AUTH_KS, legacy_table_name);
|
||||
}
|
||||
|
||||
future<> password_authenticator::migrate_legacy_metadata() const {
|
||||
plogger.info("Starting migration of legacy authentication metadata.");
|
||||
static const sstring query = sprint("SELECT * FROM %s.%s", meta::AUTH_KS, legacy_table_name);
|
||||
|
||||
return _qp.process(
|
||||
query,
|
||||
db::consistency_level::QUORUM).then([this](::shared_ptr<cql3::untyped_result_set> results) {
|
||||
return do_for_each(*results, [this](const cql3::untyped_result_set_row& row) {
|
||||
auto username = row.get_as<sstring>("username");
|
||||
auto salted_hash = row.get_as<sstring>(SALTED_HASH);
|
||||
|
||||
return _qp.process(
|
||||
update_row_query,
|
||||
consistency_for_user(username),
|
||||
{std::move(salted_hash), username}).discard_result();
|
||||
}).finally([results] {});
|
||||
}).then([] {
|
||||
plogger.info("Finished migrating legacy authentication metadata.");
|
||||
}).handle_exception([](std::exception_ptr ep) {
|
||||
plogger.error("Encountered an error during migration!");
|
||||
std::rethrow_exception(ep);
|
||||
});
|
||||
}
|
||||
|
||||
future<> password_authenticator::create_default_if_missing() const {
|
||||
return default_role_row_satisfies(_qp, &has_salted_hash).then([this](bool exists) {
|
||||
if (!exists) {
|
||||
return _qp.process(
|
||||
update_row_query,
|
||||
db::consistency_level::QUORUM,
|
||||
{hashpw(DEFAULT_USER_PASSWORD), DEFAULT_USER_NAME}).then([](auto&&) {
|
||||
plogger.info("Created default superuser authentication record.");
|
||||
return make_ready_future<>();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
return make_ready_future<>();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
future<> password_authenticator::start() {
|
||||
return once_among_shards([this] {
|
||||
gensalt(); // do this once to determine usable hashing
|
||||
|
||||
auto f = create_metadata_table_if_missing(
|
||||
meta::roles_table::name,
|
||||
_qp,
|
||||
meta::roles_table::creation_query(),
|
||||
_migration_manager);
|
||||
|
||||
_stopped = do_after_system_ready(_as, [this] {
|
||||
return async([this] {
|
||||
wait_for_schema_agreement(_migration_manager, _qp.db().local()).get0();
|
||||
|
||||
if (any_nondefault_role_row_satisfies(_qp, &has_salted_hash).get0()) {
|
||||
if (legacy_metadata_exists()) {
|
||||
plogger.warn("Ignoring legacy authentication metadata since nondefault data already exist.");
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (legacy_metadata_exists()) {
|
||||
migrate_legacy_metadata().get0();
|
||||
return;
|
||||
}
|
||||
|
||||
create_default_if_missing().get0();
|
||||
});
|
||||
});
|
||||
|
||||
return f;
|
||||
});
|
||||
}
|
||||
|
||||
future<> password_authenticator::stop() {
|
||||
_as.request_abort();
|
||||
return _stopped.handle_exception_type([] (const sleep_aborted&) { });
|
||||
future<> auth::password_authenticator::stop() {
|
||||
return make_ready_future<>();
|
||||
}
|
||||
|
||||
db::consistency_level password_authenticator::consistency_for_user(stdx::string_view role_name) {
|
||||
if (role_name == DEFAULT_USER_NAME) {
|
||||
db::consistency_level auth::password_authenticator::consistency_for_user(const sstring& username) {
|
||||
if (username == DEFAULT_USER_NAME) {
|
||||
return db::consistency_level::QUORUM;
|
||||
}
|
||||
return db::consistency_level::LOCAL_ONE;
|
||||
}
|
||||
|
||||
const sstring& password_authenticator::qualified_java_name() const {
|
||||
const sstring& auth::password_authenticator::qualified_java_name() const {
|
||||
return password_authenticator_name();
|
||||
}
|
||||
|
||||
bool password_authenticator::require_authentication() const {
|
||||
bool auth::password_authenticator::require_authentication() const {
|
||||
return true;
|
||||
}
|
||||
|
||||
authentication_option_set password_authenticator::supported_options() const {
|
||||
return authentication_option_set{authentication_option::password};
|
||||
auth::authenticator::option_set auth::password_authenticator::supported_options() const {
|
||||
return option_set::of<option::PASSWORD>();
|
||||
}
|
||||
|
||||
authentication_option_set password_authenticator::alterable_options() const {
|
||||
return authentication_option_set{authentication_option::password};
|
||||
auth::authenticator::option_set auth::password_authenticator::alterable_options() const {
|
||||
return option_set::of<option::PASSWORD>();
|
||||
}
|
||||
|
||||
future<authenticated_user> password_authenticator::authenticate(
|
||||
future<::shared_ptr<auth::authenticated_user> > auth::password_authenticator::authenticate(
|
||||
const credentials_map& credentials) const {
|
||||
if (!credentials.count(USERNAME_KEY)) {
|
||||
throw exceptions::authentication_exception(sprint("Required key '%s' is missing", USERNAME_KEY));
|
||||
@@ -299,24 +242,16 @@ future<authenticated_user> password_authenticator::authenticate(
|
||||
// Rely on query processing caching statements instead, and lets assume
|
||||
// that a map lookup string->statement is not gonna kill us much.
|
||||
return futurize_apply([this, username, password] {
|
||||
static const sstring query = sprint(
|
||||
"SELECT %s FROM %s WHERE %s = ?",
|
||||
SALTED_HASH,
|
||||
meta::roles_table::qualified_name(),
|
||||
meta::roles_table::role_col_name);
|
||||
|
||||
return _qp.process(
|
||||
query,
|
||||
consistency_for_user(username),
|
||||
{username},
|
||||
true);
|
||||
return _qp.process(sprint("SELECT %s FROM %s.%s WHERE %s = ?", SALTED_HASH,
|
||||
meta::AUTH_KS, CREDENTIALS_CF, USER_NAME),
|
||||
consistency_for_user(username), {username}, true);
|
||||
}).then_wrapped([=](future<::shared_ptr<cql3::untyped_result_set>> f) {
|
||||
try {
|
||||
auto res = f.get0();
|
||||
if (res->empty() || !checkpw(password, res->one().get_as<sstring>(SALTED_HASH))) {
|
||||
throw exceptions::authentication_exception("Username and/or password are incorrect");
|
||||
}
|
||||
return make_ready_future<authenticated_user>(username);
|
||||
return make_ready_future<::shared_ptr<authenticated_user>>(::make_shared<authenticated_user>(username));
|
||||
} catch (std::system_error &) {
|
||||
std::throw_with_nested(exceptions::authentication_exception("Could not verify password"));
|
||||
} catch (exceptions::request_execution_exception& e) {
|
||||
@@ -327,60 +262,52 @@ future<authenticated_user> password_authenticator::authenticate(
|
||||
});
|
||||
}
|
||||
|
||||
future<> password_authenticator::create(stdx::string_view role_name, const authentication_options& options) const {
|
||||
if (!options.password) {
|
||||
return make_ready_future<>();
|
||||
future<> auth::password_authenticator::create(sstring username,
|
||||
const option_map& options) {
|
||||
try {
|
||||
auto password = boost::any_cast<sstring>(options.at(option::PASSWORD));
|
||||
auto query = sprint("INSERT INTO %s.%s (%s, %s) VALUES (?, ?)",
|
||||
meta::AUTH_KS, CREDENTIALS_CF, USER_NAME, SALTED_HASH);
|
||||
return _qp.process(query, consistency_for_user(username), { username, hashpw(password) }).discard_result();
|
||||
} catch (std::out_of_range&) {
|
||||
throw exceptions::invalid_request_exception("PasswordAuthenticator requires PASSWORD option");
|
||||
}
|
||||
|
||||
return _qp.process(
|
||||
update_row_query,
|
||||
consistency_for_user(role_name),
|
||||
{hashpw(*options.password), sstring(role_name)}).discard_result();
|
||||
}
|
||||
|
||||
future<> password_authenticator::alter(stdx::string_view role_name, const authentication_options& options) const {
|
||||
if (!options.password) {
|
||||
return make_ready_future<>();
|
||||
future<> auth::password_authenticator::alter(sstring username,
|
||||
const option_map& options) {
|
||||
try {
|
||||
auto password = boost::any_cast<sstring>(options.at(option::PASSWORD));
|
||||
auto query = sprint("UPDATE %s.%s SET %s = ? WHERE %s = ?",
|
||||
meta::AUTH_KS, CREDENTIALS_CF, SALTED_HASH, USER_NAME);
|
||||
return _qp.process(query, consistency_for_user(username), { hashpw(password), username }).discard_result();
|
||||
} catch (std::out_of_range&) {
|
||||
throw exceptions::invalid_request_exception("PasswordAuthenticator requires PASSWORD option");
|
||||
}
|
||||
|
||||
static const sstring query = sprint(
|
||||
"UPDATE %s SET %s = ? WHERE %s = ?",
|
||||
meta::roles_table::qualified_name(),
|
||||
SALTED_HASH,
|
||||
meta::roles_table::role_col_name);
|
||||
|
||||
return _qp.process(
|
||||
query,
|
||||
consistency_for_user(role_name),
|
||||
{hashpw(*options.password), sstring(role_name)}).discard_result();
|
||||
}
|
||||
|
||||
future<> password_authenticator::drop(stdx::string_view name) const {
|
||||
static const sstring query = sprint(
|
||||
"DELETE %s FROM %s WHERE %s = ?",
|
||||
SALTED_HASH,
|
||||
meta::roles_table::qualified_name(),
|
||||
meta::roles_table::role_col_name);
|
||||
|
||||
return _qp.process(query, consistency_for_user(name), {sstring(name)}).discard_result();
|
||||
future<> auth::password_authenticator::drop(sstring username) {
|
||||
try {
|
||||
auto query = sprint("DELETE FROM %s.%s WHERE %s = ?",
|
||||
meta::AUTH_KS, CREDENTIALS_CF, USER_NAME);
|
||||
return _qp.process(query, consistency_for_user(username), { username }).discard_result();
|
||||
} catch (std::out_of_range&) {
|
||||
throw exceptions::invalid_request_exception("PasswordAuthenticator requires PASSWORD option");
|
||||
}
|
||||
}
|
||||
|
||||
future<custom_options> password_authenticator::query_custom_options(stdx::string_view role_name) const {
|
||||
return make_ready_future<custom_options>();
|
||||
const auth::resource_ids& auth::password_authenticator::protected_resources() const {
|
||||
static const resource_ids ids({ data_resource(meta::AUTH_KS, CREDENTIALS_CF) });
|
||||
return ids;
|
||||
}
|
||||
|
||||
const resource_set& password_authenticator::protected_resources() const {
|
||||
static const resource_set resources({make_data_resource(meta::AUTH_KS, meta::roles_table::name)});
|
||||
return resources;
|
||||
}
|
||||
|
||||
::shared_ptr<authenticator::sasl_challenge> password_authenticator::new_sasl_challenge() const {
|
||||
class plain_text_password_challenge : public sasl_challenge {
|
||||
::shared_ptr<auth::authenticator::sasl_challenge> auth::password_authenticator::new_sasl_challenge() const {
|
||||
class plain_text_password_challenge: public sasl_challenge {
|
||||
const password_authenticator& _self;
|
||||
|
||||
public:
|
||||
plain_text_password_challenge(const password_authenticator& self) : _self(self) {
|
||||
}
|
||||
plain_text_password_challenge(const password_authenticator& self) : _self(self)
|
||||
{}
|
||||
|
||||
/**
|
||||
* SASL PLAIN mechanism specifies that credentials are encoded in a
|
||||
@@ -430,12 +357,10 @@ const resource_set& password_authenticator::protected_resources() const {
|
||||
_complete = true;
|
||||
return {};
|
||||
}
|
||||
|
||||
bool is_complete() const override {
|
||||
return _complete;
|
||||
}
|
||||
|
||||
future<authenticated_user> get_authenticated_user() const override {
|
||||
future<::shared_ptr<authenticated_user>> get_authenticated_user() const override {
|
||||
return _self.authenticate(_credentials);
|
||||
}
|
||||
private:
|
||||
@@ -445,4 +370,49 @@ const resource_set& password_authenticator::protected_resources() const {
|
||||
return ::make_shared<plain_text_password_challenge>(*this);
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// Similar in structure to `auth::service::has_existing_users()`, but trying to generalize the pattern breaks all kinds
|
||||
// of module boundaries and leaks implementation details.
|
||||
//
|
||||
future<bool> auth::password_authenticator::has_existing_users() const {
|
||||
static const sstring default_user_query = sprint(
|
||||
"SELECT * FROM %s.%s WHERE %s = ?",
|
||||
meta::AUTH_KS,
|
||||
CREDENTIALS_CF,
|
||||
USER_NAME);
|
||||
|
||||
static const sstring all_users_query = sprint(
|
||||
"SELECT * FROM %s.%s LIMIT 1",
|
||||
meta::AUTH_KS,
|
||||
CREDENTIALS_CF);
|
||||
|
||||
// This logic is borrowed directly from Apache Cassandra. By first checking for the presence of the default user, we
|
||||
// can potentially avoid doing a range query with a high consistency level.
|
||||
|
||||
return _qp.process(
|
||||
default_user_query,
|
||||
db::consistency_level::ONE,
|
||||
{ meta::DEFAULT_SUPERUSER_NAME },
|
||||
true).then([this](auto results) {
|
||||
if (!results->empty()) {
|
||||
return make_ready_future<bool>(true);
|
||||
}
|
||||
|
||||
return _qp.process(
|
||||
default_user_query,
|
||||
db::consistency_level::QUORUM,
|
||||
{ meta::DEFAULT_SUPERUSER_NAME },
|
||||
true).then([this](auto results) {
|
||||
if (!results->empty()) {
|
||||
return make_ready_future<bool>(true);
|
||||
}
|
||||
|
||||
return _qp.process(
|
||||
all_users_query,
|
||||
db::consistency_level::QUORUM).then([](auto results) {
|
||||
return make_ready_future<bool>(!results->empty());
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -41,10 +41,9 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <seastar/core/abort_source.hh>
|
||||
|
||||
#include "auth/authenticator.hh"
|
||||
#include "authenticator.hh"
|
||||
#include "cql3/query_processor.hh"
|
||||
#include "delayed_tasks.hh"
|
||||
|
||||
namespace service {
|
||||
class migration_manager;
|
||||
@@ -56,49 +55,35 @@ const sstring& password_authenticator_name();
|
||||
|
||||
class password_authenticator : public authenticator {
|
||||
cql3::query_processor& _qp;
|
||||
|
||||
::service::migration_manager& _migration_manager;
|
||||
future<> _stopped;
|
||||
seastar::abort_source _as;
|
||||
|
||||
delayed_tasks<> _delayed{};
|
||||
|
||||
public:
|
||||
static db::consistency_level consistency_for_user(stdx::string_view role_name);
|
||||
|
||||
password_authenticator(cql3::query_processor&, ::service::migration_manager&);
|
||||
|
||||
~password_authenticator();
|
||||
|
||||
virtual future<> start() override;
|
||||
future<> start() override;
|
||||
|
||||
virtual future<> stop() override;
|
||||
future<> stop() override;
|
||||
|
||||
virtual const sstring& qualified_java_name() const override;
|
||||
const sstring& qualified_java_name() const override;
|
||||
bool require_authentication() const override;
|
||||
option_set supported_options() const override;
|
||||
option_set alterable_options() const override;
|
||||
future<::shared_ptr<authenticated_user>> authenticate(const credentials_map& credentials) const override;
|
||||
future<> create(sstring username, const option_map& options) override;
|
||||
future<> alter(sstring username, const option_map& options) override;
|
||||
future<> drop(sstring username) override;
|
||||
const resource_ids& protected_resources() const override;
|
||||
::shared_ptr<sasl_challenge> new_sasl_challenge() const override;
|
||||
|
||||
virtual bool require_authentication() const override;
|
||||
|
||||
virtual authentication_option_set supported_options() const override;
|
||||
|
||||
virtual authentication_option_set alterable_options() const override;
|
||||
|
||||
virtual future<authenticated_user> authenticate(const credentials_map& credentials) const override;
|
||||
|
||||
virtual future<> create(stdx::string_view role_name, const authentication_options& options) const override;
|
||||
|
||||
virtual future<> alter(stdx::string_view role_name, const authentication_options& options) const override;
|
||||
|
||||
virtual future<> drop(stdx::string_view role_name) const override;
|
||||
|
||||
virtual future<custom_options> query_custom_options(stdx::string_view role_name) const override;
|
||||
|
||||
virtual const resource_set& protected_resources() const override;
|
||||
|
||||
virtual ::shared_ptr<sasl_challenge> new_sasl_challenge() const override;
|
||||
static db::consistency_level consistency_for_user(const sstring& username);
|
||||
|
||||
private:
|
||||
bool legacy_metadata_exists() const;
|
||||
|
||||
future<> migrate_legacy_metadata() const;
|
||||
|
||||
future<> create_default_if_missing() const;
|
||||
future<bool> has_existing_users() const;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -39,33 +39,32 @@
|
||||
* along with Scylla. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "auth/permission.hh"
|
||||
|
||||
#include <boost/algorithm/string.hpp>
|
||||
|
||||
#include <unordered_map>
|
||||
#include <boost/algorithm/string.hpp>
|
||||
#include "permission.hh"
|
||||
|
||||
const auth::permission_set auth::permissions::ALL = auth::permission_set::of<
|
||||
auth::permission::CREATE,
|
||||
auth::permission::ALTER,
|
||||
auth::permission::DROP,
|
||||
auth::permission::SELECT,
|
||||
auth::permission::MODIFY,
|
||||
auth::permission::AUTHORIZE,
|
||||
auth::permission::DESCRIBE>();
|
||||
|
||||
const auth::permission_set auth::permissions::ALL_DATA =
|
||||
auth::permission_set::of<auth::permission::CREATE,
|
||||
auth::permission::ALTER, auth::permission::DROP,
|
||||
auth::permission::SELECT,
|
||||
auth::permission::MODIFY,
|
||||
auth::permission::AUTHORIZE>();
|
||||
const auth::permission_set auth::permissions::ALL = auth::permissions::ALL_DATA;
|
||||
const auth::permission_set auth::permissions::NONE;
|
||||
const auth::permission_set auth::permissions::ALTERATIONS =
|
||||
auth::permission_set::of<auth::permission::CREATE,
|
||||
auth::permission::ALTER, auth::permission::DROP>();
|
||||
|
||||
static const std::unordered_map<sstring, auth::permission> permission_names({
|
||||
{"READ", auth::permission::READ},
|
||||
{"WRITE", auth::permission::WRITE},
|
||||
{"CREATE", auth::permission::CREATE},
|
||||
{"ALTER", auth::permission::ALTER},
|
||||
{"DROP", auth::permission::DROP},
|
||||
{"SELECT", auth::permission::SELECT},
|
||||
{"MODIFY", auth::permission::MODIFY},
|
||||
{"AUTHORIZE", auth::permission::AUTHORIZE},
|
||||
{"DESCRIBE", auth::permission::DESCRIBE}});
|
||||
{ "READ", auth::permission::READ },
|
||||
{ "WRITE", auth::permission::WRITE },
|
||||
{ "CREATE", auth::permission::CREATE },
|
||||
{ "ALTER", auth::permission::ALTER },
|
||||
{ "DROP", auth::permission::DROP },
|
||||
{ "SELECT", auth::permission::SELECT },
|
||||
{ "MODIFY", auth::permission::MODIFY },
|
||||
{ "AUTHORIZE", auth::permission::AUTHORIZE },
|
||||
});
|
||||
|
||||
const sstring& auth::permissions::to_string(permission p) {
|
||||
for (auto& v : permission_names) {
|
||||
|
||||
@@ -42,11 +42,10 @@
|
||||
#pragma once
|
||||
|
||||
#include <unordered_set>
|
||||
|
||||
#include <seastar/core/sstring.hh>
|
||||
|
||||
#include "enum_set.hh"
|
||||
#include "seastarx.hh"
|
||||
#include "enum_set.hh"
|
||||
|
||||
namespace auth {
|
||||
|
||||
@@ -67,13 +66,9 @@ enum class permission {
|
||||
|
||||
// permission management
|
||||
AUTHORIZE, // required for GRANT and REVOKE.
|
||||
DESCRIBE, // required on the root-level role resource to list all roles.
|
||||
|
||||
};
|
||||
|
||||
typedef enum_set<
|
||||
super_enum<
|
||||
permission,
|
||||
typedef enum_set<super_enum<permission,
|
||||
permission::READ,
|
||||
permission::WRITE,
|
||||
permission::CREATE,
|
||||
@@ -81,15 +76,16 @@ typedef enum_set<
|
||||
permission::DROP,
|
||||
permission::SELECT,
|
||||
permission::MODIFY,
|
||||
permission::AUTHORIZE,
|
||||
permission::DESCRIBE>> permission_set;
|
||||
permission::AUTHORIZE>> permission_set;
|
||||
|
||||
bool operator<(const permission_set&, const permission_set&);
|
||||
|
||||
namespace permissions {
|
||||
|
||||
extern const permission_set ALL_DATA;
|
||||
extern const permission_set ALL;
|
||||
extern const permission_set NONE;
|
||||
extern const permission_set ALTERATIONS;
|
||||
|
||||
const sstring& to_string(permission);
|
||||
permission from_string(const sstring&);
|
||||
@@ -97,6 +93,7 @@ permission from_string(const sstring&);
|
||||
std::unordered_set<sstring> to_strings(const permission_set&);
|
||||
permission_set from_strings(const std::unordered_set<sstring>&);
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -39,15 +39,13 @@ permissions_cache_config permissions_cache_config::from_db_config(const db::conf
|
||||
|
||||
permissions_cache::permissions_cache(const permissions_cache_config& c, service& ser, logging::logger& log)
|
||||
: _cache(c.max_entries, c.validity_period, c.update_period, log, [&ser, &log](const key_type& k) {
|
||||
log.debug("Refreshing permissions for {}", k.first);
|
||||
return ser.get_uncached_permissions(k.first, k.second);
|
||||
log.debug("Refreshing permissions for {}", k.first.name());
|
||||
return ser.underlying_authorizer().authorize(ser, ::make_shared<authenticated_user>(k.first), k.second);
|
||||
}) {
|
||||
}
|
||||
|
||||
future<permission_set> permissions_cache::get(const role_or_anonymous& maybe_role, const resource& r) {
|
||||
return do_with(key_type(maybe_role, r), [this](const auto& k) {
|
||||
return _cache.get(k);
|
||||
});
|
||||
future<permission_set> permissions_cache::get(::shared_ptr<authenticated_user> user, data_resource r) {
|
||||
return _cache.get(key_type(*user, r));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -22,29 +22,37 @@
|
||||
#pragma once
|
||||
|
||||
#include <chrono>
|
||||
#include <experimental/string_view>
|
||||
#include <functional>
|
||||
#include <iostream>
|
||||
#include <optional>
|
||||
#include <utility>
|
||||
|
||||
#include <seastar/core/future.hh>
|
||||
#include <seastar/core/shared_ptr.hh>
|
||||
#include <seastar/core/sstring.hh>
|
||||
|
||||
#include "auth/authenticated_user.hh"
|
||||
#include "auth/data_resource.hh"
|
||||
#include "auth/permission.hh"
|
||||
#include "auth/resource.hh"
|
||||
#include "auth/role_or_anonymous.hh"
|
||||
#include "log.hh"
|
||||
#include "stdx.hh"
|
||||
#include "utils/hash.hh"
|
||||
#include "utils/loading_cache.hh"
|
||||
|
||||
namespace std {
|
||||
|
||||
inline std::ostream& operator<<(std::ostream& os, const pair<auth::role_or_anonymous, auth::resource>& p) {
|
||||
os << "{role: " << p.first << ", resource: " << p.second << "}";
|
||||
template <>
|
||||
struct hash<auth::data_resource> final {
|
||||
size_t operator()(const auth::data_resource & v) const {
|
||||
return v.hash_value();
|
||||
}
|
||||
};
|
||||
|
||||
template <>
|
||||
struct hash<auth::authenticated_user> final {
|
||||
size_t operator()(const auth::authenticated_user & v) const {
|
||||
return utils::tuple_hash()(v.name(), v.is_anonymous());
|
||||
}
|
||||
};
|
||||
|
||||
inline std::ostream& operator<<(std::ostream& os, const std::pair<auth::authenticated_user, auth::data_resource>& p) {
|
||||
os << "{user: " << p.first.name() << ", data_resource: " << p.second << "}";
|
||||
return os;
|
||||
}
|
||||
|
||||
@@ -68,7 +76,7 @@ struct permissions_cache_config final {
|
||||
|
||||
class permissions_cache final {
|
||||
using cache_type = utils::loading_cache<
|
||||
std::pair<role_or_anonymous, resource>,
|
||||
std::pair<authenticated_user, data_resource>,
|
||||
permission_set,
|
||||
utils::loading_cache_reload_enabled::yes,
|
||||
utils::simple_entry_size<permission_set>,
|
||||
@@ -85,7 +93,7 @@ public:
|
||||
return _cache.stop();
|
||||
}
|
||||
|
||||
future<permission_set> get(const role_or_anonymous&, const resource&);
|
||||
future<permission_set> get(::shared_ptr<authenticated_user>, data_resource);
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
296
auth/resource.cc
296
auth/resource.cc
@@ -1,296 +0,0 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Copyright (C) 2016 ScyllaDB
|
||||
*
|
||||
* Modified by ScyllaDB
|
||||
*/
|
||||
|
||||
/*
|
||||
* This file is part of Scylla.
|
||||
*
|
||||
* Scylla is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Scylla is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Scylla. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "auth/resource.hh"
|
||||
|
||||
#include <algorithm>
|
||||
#include <iterator>
|
||||
#include <unordered_map>
|
||||
|
||||
#include <boost/algorithm/string/join.hpp>
|
||||
#include <boost/algorithm/string/split.hpp>
|
||||
|
||||
#include "service/storage_proxy.hh"
|
||||
|
||||
namespace auth {
|
||||
|
||||
std::ostream& operator<<(std::ostream& os, resource_kind kind) {
|
||||
switch (kind) {
|
||||
case resource_kind::data: os << "data"; break;
|
||||
case resource_kind::role: os << "role"; break;
|
||||
}
|
||||
|
||||
return os;
|
||||
}
|
||||
|
||||
static const std::unordered_map<resource_kind, stdx::string_view> roots{
|
||||
{resource_kind::data, "data"},
|
||||
{resource_kind::role, "roles"}};
|
||||
|
||||
static const std::unordered_map<resource_kind, std::size_t> max_parts{
|
||||
{resource_kind::data, 2},
|
||||
{resource_kind::role, 1}};
|
||||
|
||||
static permission_set applicable_permissions(const data_resource_view& dv) {
|
||||
if (dv.table()) {
|
||||
return permission_set::of<
|
||||
permission::ALTER,
|
||||
permission::DROP,
|
||||
permission::SELECT,
|
||||
permission::MODIFY,
|
||||
permission::AUTHORIZE>();
|
||||
}
|
||||
|
||||
return permission_set::of<
|
||||
permission::CREATE,
|
||||
permission::ALTER,
|
||||
permission::DROP,
|
||||
permission::SELECT,
|
||||
permission::MODIFY,
|
||||
permission::AUTHORIZE>();
|
||||
}
|
||||
|
||||
static permission_set applicable_permissions(const role_resource_view& rv) {
|
||||
if (rv.role()) {
|
||||
return permission_set::of<permission::ALTER, permission::DROP, permission::AUTHORIZE>();
|
||||
}
|
||||
|
||||
return permission_set::of<
|
||||
permission::CREATE,
|
||||
permission::ALTER,
|
||||
permission::DROP,
|
||||
permission::AUTHORIZE,
|
||||
permission::DESCRIBE>();
|
||||
}
|
||||
|
||||
resource::resource(resource_kind kind) : _kind(kind), _parts{sstring(roots.at(kind))} {
|
||||
}
|
||||
|
||||
resource::resource(resource_kind kind, std::vector<sstring> parts) : resource(kind) {
|
||||
_parts.reserve(parts.size() + 1);
|
||||
_parts.insert(_parts.end(), std::make_move_iterator(parts.begin()), std::make_move_iterator(parts.end()));
|
||||
}
|
||||
|
||||
resource::resource(data_resource_t, stdx::string_view keyspace)
|
||||
: resource(resource_kind::data, std::vector<sstring>{sstring(keyspace)}) {
|
||||
}
|
||||
|
||||
resource::resource(data_resource_t, stdx::string_view keyspace, stdx::string_view table)
|
||||
: resource(resource_kind::data, std::vector<sstring>{sstring(keyspace), sstring(table)}) {
|
||||
}
|
||||
|
||||
resource::resource(role_resource_t, stdx::string_view role)
|
||||
: resource(resource_kind::role, std::vector<sstring>{sstring(role)}) {
|
||||
}
|
||||
|
||||
sstring resource::name() const {
|
||||
return boost::algorithm::join(_parts, "/");
|
||||
}
|
||||
|
||||
std::optional<resource> resource::parent() const {
|
||||
if (_parts.size() == 1) {
|
||||
return {};
|
||||
}
|
||||
|
||||
resource copy = *this;
|
||||
copy._parts.pop_back();
|
||||
return copy;
|
||||
}
|
||||
|
||||
permission_set resource::applicable_permissions() const {
|
||||
permission_set ps;
|
||||
|
||||
switch (_kind) {
|
||||
case resource_kind::data: ps = ::auth::applicable_permissions(data_resource_view(*this)); break;
|
||||
case resource_kind::role: ps = ::auth::applicable_permissions(role_resource_view(*this)); break;
|
||||
}
|
||||
|
||||
return ps;
|
||||
}
|
||||
|
||||
bool operator<(const resource& r1, const resource& r2) {
|
||||
if (r1._kind != r2._kind) {
|
||||
return r1._kind < r2._kind;
|
||||
}
|
||||
|
||||
return std::lexicographical_compare(
|
||||
r1._parts.cbegin() + 1,
|
||||
r1._parts.cend(),
|
||||
r2._parts.cbegin() + 1,
|
||||
r2._parts.cend());
|
||||
}
|
||||
|
||||
std::ostream& operator<<(std::ostream& os, const resource& r) {
|
||||
switch (r.kind()) {
|
||||
case resource_kind::data: return os << data_resource_view(r);
|
||||
case resource_kind::role: return os << role_resource_view(r);
|
||||
}
|
||||
|
||||
return os;
|
||||
}
|
||||
|
||||
data_resource_view::data_resource_view(const resource& r) : _resource(r) {
|
||||
if (r._kind != resource_kind::data) {
|
||||
throw resource_kind_mismatch(resource_kind::data, r._kind);
|
||||
}
|
||||
}
|
||||
|
||||
std::optional<stdx::string_view> data_resource_view::keyspace() const {
|
||||
if (_resource._parts.size() == 1) {
|
||||
return {};
|
||||
}
|
||||
|
||||
return _resource._parts[1];
|
||||
}
|
||||
|
||||
std::optional<stdx::string_view> data_resource_view::table() const {
|
||||
if (_resource._parts.size() <= 2) {
|
||||
return {};
|
||||
}
|
||||
|
||||
return _resource._parts[2];
|
||||
}
|
||||
|
||||
std::ostream& operator<<(std::ostream& os, const data_resource_view& v) {
|
||||
const auto keyspace = v.keyspace();
|
||||
const auto table = v.table();
|
||||
|
||||
if (!keyspace) {
|
||||
os << "<all keyspaces>";
|
||||
} else if (!table) {
|
||||
os << "<keyspace " << *keyspace << '>';
|
||||
} else {
|
||||
os << "<table " << *keyspace << '.' << *table << '>';
|
||||
}
|
||||
|
||||
return os;
|
||||
}
|
||||
|
||||
role_resource_view::role_resource_view(const resource& r) : _resource(r) {
|
||||
if (r._kind != resource_kind::role) {
|
||||
throw resource_kind_mismatch(resource_kind::role, r._kind);
|
||||
}
|
||||
}
|
||||
|
||||
std::optional<stdx::string_view> role_resource_view::role() const {
|
||||
if (_resource._parts.size() == 1) {
|
||||
return {};
|
||||
}
|
||||
|
||||
return _resource._parts[1];
|
||||
}
|
||||
|
||||
std::ostream& operator<<(std::ostream& os, const role_resource_view& v) {
|
||||
const auto role = v.role();
|
||||
|
||||
if (!role) {
|
||||
os << "<all roles>";
|
||||
} else {
|
||||
os << "<role " << *role << '>';
|
||||
}
|
||||
|
||||
return os;
|
||||
}
|
||||
|
||||
resource parse_resource(stdx::string_view name) {
|
||||
static const std::unordered_map<stdx::string_view, resource_kind> reverse_roots = [] {
|
||||
std::unordered_map<stdx::string_view, resource_kind> result;
|
||||
|
||||
for (const auto& pair : roots) {
|
||||
result.emplace(pair.second, pair.first);
|
||||
}
|
||||
|
||||
return result;
|
||||
}();
|
||||
|
||||
std::vector<sstring> parts;
|
||||
boost::split(parts, name, [](char ch) { return ch == '/'; });
|
||||
|
||||
if (parts.empty()) {
|
||||
throw invalid_resource_name(name);
|
||||
}
|
||||
|
||||
const auto iter = reverse_roots.find(parts[0]);
|
||||
if (iter == reverse_roots.end()) {
|
||||
throw invalid_resource_name(name);
|
||||
}
|
||||
|
||||
const auto kind = iter->second;
|
||||
parts.erase(parts.begin());
|
||||
|
||||
if (parts.size() > max_parts.at(kind)) {
|
||||
throw invalid_resource_name(name);
|
||||
}
|
||||
|
||||
return resource(kind, std::move(parts));
|
||||
}
|
||||
|
||||
static const resource the_root_data_resource{resource_kind::data};
|
||||
|
||||
const resource& root_data_resource() {
|
||||
return the_root_data_resource;
|
||||
}
|
||||
|
||||
static const resource the_root_role_resource{resource_kind::role};
|
||||
|
||||
const resource& root_role_resource() {
|
||||
return the_root_role_resource;
|
||||
}
|
||||
|
||||
resource_set expand_resource_family(const resource& rr) {
|
||||
resource r = rr;
|
||||
resource_set rs;
|
||||
|
||||
while (true) {
|
||||
const auto pr = r.parent();
|
||||
rs.insert(std::move(r));
|
||||
|
||||
if (!pr) {
|
||||
break;
|
||||
}
|
||||
|
||||
r = std::move(*pr);
|
||||
}
|
||||
|
||||
return rs;
|
||||
}
|
||||
|
||||
}
|
||||
254
auth/resource.hh
254
auth/resource.hh
@@ -1,254 +0,0 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Copyright (C) 2016 ScyllaDB
|
||||
*
|
||||
* Modified by ScyllaDB
|
||||
*/
|
||||
|
||||
/*
|
||||
* This file is part of Scylla.
|
||||
*
|
||||
* Scylla is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Scylla is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Scylla. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <experimental/string_view>
|
||||
#include <iostream>
|
||||
#include <optional>
|
||||
#include <stdexcept>
|
||||
#include <tuple>
|
||||
#include <vector>
|
||||
#include <unordered_set>
|
||||
|
||||
#include <seastar/core/print.hh>
|
||||
#include <seastar/core/sstring.hh>
|
||||
|
||||
#include "auth/permission.hh"
|
||||
#include "seastarx.hh"
|
||||
#include "stdx.hh"
|
||||
#include "utils/hash.hh"
|
||||
|
||||
namespace auth {
|
||||
|
||||
class invalid_resource_name : public std::invalid_argument {
|
||||
public:
|
||||
explicit invalid_resource_name(stdx::string_view name)
|
||||
: std::invalid_argument(sprint("The resource name '%s' is invalid.", name)) {
|
||||
}
|
||||
};
|
||||
|
||||
enum class resource_kind {
|
||||
data, role
|
||||
};
|
||||
|
||||
std::ostream& operator<<(std::ostream&, resource_kind);
|
||||
|
||||
///
|
||||
/// Type tag for constructing data resources.
|
||||
///
|
||||
struct data_resource_t final {};
|
||||
|
||||
///
|
||||
/// Type tag for constructing role resources.
|
||||
///
|
||||
struct role_resource_t final {};
|
||||
|
||||
///
|
||||
/// Resources are entities that users can be granted permissions on.
|
||||
///
|
||||
/// There are data (keyspaces and tables) and role resources. There may be other kinds of resources in the future.
|
||||
///
|
||||
/// When they are stored as system metadata, resources have the form `root/part_0/part_1/.../part_n`. Each kind of
|
||||
/// resource has a specific root prefix, followed by a maximum of `n` parts (where `n` is distinct for each kind of
|
||||
/// resource as well). In this code, this form is called the "name".
|
||||
///
|
||||
/// Since all resources have this same structure, all the different kinds are stored in instances of the same class:
|
||||
/// \ref resource. When we wish to query a resource for kind-specific data (like the table of a "data" resource), we
|
||||
/// create a kind-specific "view" of the resource.
|
||||
///
|
||||
class resource final {
|
||||
resource_kind _kind;
|
||||
|
||||
std::vector<sstring> _parts;
|
||||
|
||||
public:
|
||||
///
|
||||
/// A root resource of a particular kind.
|
||||
///
|
||||
explicit resource(resource_kind);
|
||||
resource(data_resource_t, stdx::string_view keyspace);
|
||||
resource(data_resource_t, stdx::string_view keyspace, stdx::string_view table);
|
||||
resource(role_resource_t, stdx::string_view role);
|
||||
|
||||
resource_kind kind() const noexcept {
|
||||
return _kind;
|
||||
}
|
||||
|
||||
///
|
||||
/// A machine-friendly identifier unique to each resource.
|
||||
///
|
||||
sstring name() const;
|
||||
|
||||
std::optional<resource> parent() const;
|
||||
|
||||
permission_set applicable_permissions() const;
|
||||
|
||||
private:
|
||||
resource(resource_kind, std::vector<sstring> parts);
|
||||
|
||||
friend class std::hash<resource>;
|
||||
friend class data_resource_view;
|
||||
friend class role_resource_view;
|
||||
|
||||
friend bool operator<(const resource&, const resource&);
|
||||
friend bool operator==(const resource&, const resource&);
|
||||
friend resource parse_resource(stdx::string_view);
|
||||
};
|
||||
|
||||
bool operator<(const resource&, const resource&);
|
||||
|
||||
inline bool operator==(const resource& r1, const resource& r2) {
|
||||
return (r1._kind == r2._kind) && (r1._parts == r2._parts);
|
||||
}
|
||||
|
||||
inline bool operator!=(const resource& r1, const resource& r2) {
|
||||
return !(r1 == r2);
|
||||
}
|
||||
|
||||
std::ostream& operator<<(std::ostream&, const resource&);
|
||||
|
||||
class resource_kind_mismatch : public std::invalid_argument {
|
||||
public:
|
||||
explicit resource_kind_mismatch(resource_kind expected, resource_kind actual)
|
||||
: std::invalid_argument(
|
||||
sprint("This resource has kind '%s', but was expected to have kind '%s'.", actual, expected)) {
|
||||
}
|
||||
};
|
||||
|
||||
/// A "data" view of \ref resource.
|
||||
///
|
||||
/// If neither `keyspace` nor `table` is present, this is the root resource.
|
||||
class data_resource_view final {
|
||||
const resource& _resource;
|
||||
|
||||
public:
|
||||
///
|
||||
/// \throws `resource_kind_mismatch` if the argument is not a `data` resource.
|
||||
///
|
||||
explicit data_resource_view(const resource& r);
|
||||
|
||||
std::optional<stdx::string_view> keyspace() const;
|
||||
|
||||
std::optional<stdx::string_view> table() const;
|
||||
};
|
||||
|
||||
std::ostream& operator<<(std::ostream&, const data_resource_view&);
|
||||
|
||||
///
|
||||
/// A "role" view of \ref resource.
|
||||
///
|
||||
/// If `role` is not present, this is the root resource.
|
||||
///
|
||||
class role_resource_view final {
|
||||
const resource& _resource;
|
||||
|
||||
public:
|
||||
///
|
||||
/// \throws \ref resource_kind_mismatch if the argument is not a "role" resource.
|
||||
///
|
||||
explicit role_resource_view(const resource&);
|
||||
|
||||
std::optional<stdx::string_view> role() const;
|
||||
};
|
||||
|
||||
std::ostream& operator<<(std::ostream&, const role_resource_view&);
|
||||
|
||||
///
|
||||
/// Parse a resource from its name.
|
||||
///
|
||||
/// \throws \ref invalid_resource_name when the name is malformed.
|
||||
///
|
||||
resource parse_resource(stdx::string_view name);
|
||||
|
||||
const resource& root_data_resource();
|
||||
|
||||
inline resource make_data_resource(stdx::string_view keyspace) {
|
||||
return resource(data_resource_t{}, keyspace);
|
||||
}
|
||||
inline resource make_data_resource(stdx::string_view keyspace, stdx::string_view table) {
|
||||
return resource(data_resource_t{}, keyspace, table);
|
||||
}
|
||||
|
||||
const resource& root_role_resource();
|
||||
|
||||
inline resource make_role_resource(stdx::string_view role) {
|
||||
return resource(role_resource_t{}, role);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
namespace std {
|
||||
|
||||
template <>
|
||||
struct hash<auth::resource> {
|
||||
static size_t hash_data(const auth::data_resource_view& dv) {
|
||||
return utils::tuple_hash()(std::make_tuple(auth::resource_kind::data, dv.keyspace(), dv.table()));
|
||||
}
|
||||
|
||||
static size_t hash_role(const auth::role_resource_view& rv) {
|
||||
return utils::tuple_hash()(std::make_tuple(auth::resource_kind::role, rv.role()));
|
||||
}
|
||||
|
||||
size_t operator()(const auth::resource& r) const {
|
||||
std::size_t value;
|
||||
|
||||
switch (r._kind) {
|
||||
case auth::resource_kind::data: value = hash_data(auth::data_resource_view(r)); break;
|
||||
case auth::resource_kind::role: value = hash_role(auth::role_resource_view(r)); break;
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
namespace auth {
|
||||
|
||||
using resource_set = std::unordered_set<resource>;
|
||||
|
||||
//
|
||||
// A resource and all of its parents.
|
||||
//
|
||||
resource_set expand_resource_family(const resource&);
|
||||
|
||||
}
|
||||
@@ -1,169 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2017 ScyllaDB
|
||||
*/
|
||||
|
||||
/*
|
||||
* This file is part of Scylla.
|
||||
*
|
||||
* Scylla is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Scylla is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Scylla. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <experimental/string_view>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <stdexcept>
|
||||
#include <unordered_set>
|
||||
|
||||
#include <seastar/core/future.hh>
|
||||
#include <seastar/core/print.hh>
|
||||
#include <seastar/core/sstring.hh>
|
||||
|
||||
#include "auth/resource.hh"
|
||||
#include "seastarx.hh"
|
||||
#include "stdx.hh"
|
||||
|
||||
namespace auth {
|
||||
|
||||
struct role_config final {
|
||||
bool is_superuser{false};
|
||||
bool can_login{false};
|
||||
};
|
||||
|
||||
///
|
||||
/// Differential update for altering existing roles.
|
||||
///
|
||||
struct role_config_update final {
|
||||
std::optional<bool> is_superuser{};
|
||||
std::optional<bool> can_login{};
|
||||
};
|
||||
|
||||
///
|
||||
/// A logical argument error for a role-management operation.
|
||||
///
|
||||
class roles_argument_exception : public std::invalid_argument {
|
||||
public:
|
||||
using std::invalid_argument::invalid_argument;
|
||||
};
|
||||
|
||||
class role_already_exists : public roles_argument_exception {
|
||||
public:
|
||||
explicit role_already_exists(stdx::string_view role_name)
|
||||
: roles_argument_exception(sprint("Role %s already exists.", role_name)) {
|
||||
}
|
||||
};
|
||||
|
||||
class nonexistant_role : public roles_argument_exception {
|
||||
public:
|
||||
explicit nonexistant_role(stdx::string_view role_name)
|
||||
: roles_argument_exception(sprint("Role %s doesn't exist.", role_name)) {
|
||||
}
|
||||
};
|
||||
|
||||
class role_already_included : public roles_argument_exception {
|
||||
public:
|
||||
role_already_included(stdx::string_view grantee_name, stdx::string_view role_name)
|
||||
: roles_argument_exception(
|
||||
sprint("%s already includes role %s.", grantee_name, role_name)) {
|
||||
}
|
||||
};
|
||||
|
||||
class revoke_ungranted_role : public roles_argument_exception {
|
||||
public:
|
||||
revoke_ungranted_role(stdx::string_view revokee_name, stdx::string_view role_name)
|
||||
: roles_argument_exception(
|
||||
sprint("%s was not granted role %s, so it cannot be revoked.", revokee_name, role_name)) {
|
||||
}
|
||||
};
|
||||
|
||||
using role_set = std::unordered_set<sstring>;
|
||||
|
||||
enum class recursive_role_query { yes, no };
|
||||
|
||||
///
|
||||
/// Abstract client for managing roles.
|
||||
///
|
||||
/// All state necessary for managing roles is stored externally to the client instance.
|
||||
///
|
||||
/// All implementations should throw role-related exceptions as documented. Authorization is not addressed here, and
|
||||
/// access-control should never be enforced in implementations.
|
||||
///
|
||||
class role_manager {
|
||||
public:
|
||||
virtual ~role_manager() = default;
|
||||
|
||||
virtual stdx::string_view qualified_java_name() const noexcept = 0;
|
||||
|
||||
virtual const resource_set& protected_resources() const = 0;
|
||||
|
||||
virtual future<> start() = 0;
|
||||
|
||||
virtual future<> stop() = 0;
|
||||
|
||||
///
|
||||
/// \returns an exceptional future with \ref role_already_exists for a role that has previously been created.
|
||||
///
|
||||
virtual future<> create(stdx::string_view role_name, const role_config&) const = 0;
|
||||
|
||||
///
|
||||
/// \returns an exceptional future with \ref nonexistant_role if the role does not exist.
|
||||
///
|
||||
virtual future<> drop(stdx::string_view role_name) const = 0;
|
||||
|
||||
///
|
||||
/// \returns an exceptional future with \ref nonexistant_role if the role does not exist.
|
||||
///
|
||||
virtual future<> alter(stdx::string_view role_name, const role_config_update&) const = 0;
|
||||
|
||||
///
|
||||
/// Grant `role_name` to `grantee_name`.
|
||||
///
|
||||
/// \returns an exceptional future with \ref nonexistant_role if either the role or the grantee do not exist.
|
||||
///
|
||||
/// \returns an exceptional future with \ref role_already_included if granting the role would be redundant, or
|
||||
/// create a cycle.
|
||||
///
|
||||
virtual future<> grant(stdx::string_view grantee_name, stdx::string_view role_name) const = 0;
|
||||
|
||||
///
|
||||
/// Revoke `role_name` from `revokee_name`.
|
||||
///
|
||||
/// \returns an exceptional future with \ref nonexistant_role if either the role or the revokee do not exist.
|
||||
///
|
||||
/// \returns an exceptional future with \ref revoke_ungranted_role if the role was not granted.
|
||||
///
|
||||
virtual future<> revoke(stdx::string_view revokee_name, stdx::string_view role_name) const = 0;
|
||||
|
||||
///
|
||||
/// \returns an exceptional future with \ref nonexistant_role if the role does not exist.
|
||||
///
|
||||
virtual future<role_set> query_granted(stdx::string_view grantee, recursive_role_query) const = 0;
|
||||
|
||||
virtual future<role_set> query_all() const = 0;
|
||||
|
||||
virtual future<bool> exists(stdx::string_view role_name) const = 0;
|
||||
|
||||
///
|
||||
/// \returns an exceptional future with \ref nonexistant_role if the role does not exist.
|
||||
///
|
||||
virtual future<bool> is_superuser(stdx::string_view role_name) const = 0;
|
||||
|
||||
///
|
||||
/// \returns an exceptional future with \ref nonexistant_role if the role does not exist.
|
||||
///
|
||||
virtual future<bool> can_login(stdx::string_view role_name) const = 0;
|
||||
};
|
||||
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2018 ScyllaDB
|
||||
*/
|
||||
|
||||
/*
|
||||
* This file is part of Scylla.
|
||||
*
|
||||
* Scylla is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Scylla is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Scylla. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "auth/role_or_anonymous.hh"
|
||||
|
||||
#include <iostream>
|
||||
|
||||
namespace auth {
|
||||
|
||||
std::ostream& operator<<(std::ostream& os, const role_or_anonymous& mr) {
|
||||
os << mr.name.value_or("<anonymous>");
|
||||
return os;
|
||||
}
|
||||
|
||||
bool operator==(const role_or_anonymous& mr1, const role_or_anonymous& mr2) noexcept {
|
||||
return mr1.name == mr2.name;
|
||||
}
|
||||
|
||||
bool is_anonymous(const role_or_anonymous& mr) noexcept {
|
||||
return !mr.name.has_value();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,66 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2018 ScyllaDB
|
||||
*/
|
||||
|
||||
/*
|
||||
* This file is part of Scylla.
|
||||
*
|
||||
* Scylla is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Scylla is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Scylla. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <experimental/string_view>
|
||||
#include <functional>
|
||||
#include <iosfwd>
|
||||
#include <optional>
|
||||
|
||||
#include <seastar/core/sstring.hh>
|
||||
|
||||
#include "seastarx.hh"
|
||||
#include "stdx.hh"
|
||||
|
||||
namespace auth {
|
||||
|
||||
class role_or_anonymous final {
|
||||
public:
|
||||
std::optional<sstring> name{};
|
||||
|
||||
role_or_anonymous() = default;
|
||||
role_or_anonymous(stdx::string_view name) : name(name) {
|
||||
}
|
||||
};
|
||||
|
||||
std::ostream& operator<<(std::ostream&, const role_or_anonymous&);
|
||||
|
||||
bool operator==(const role_or_anonymous&, const role_or_anonymous&) noexcept;
|
||||
|
||||
inline bool operator!=(const role_or_anonymous& mr1, const role_or_anonymous& mr2) noexcept {
|
||||
return !(mr1 == mr2);
|
||||
}
|
||||
|
||||
bool is_anonymous(const role_or_anonymous&) noexcept;
|
||||
|
||||
}
|
||||
|
||||
namespace std {
|
||||
|
||||
template <>
|
||||
struct hash<auth::role_or_anonymous> {
|
||||
size_t operator()(const auth::role_or_anonymous& mr) const {
|
||||
return hash<std::optional<sstring>>()(mr.name);
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
@@ -1,119 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2018 ScyllaDB
|
||||
*/
|
||||
|
||||
/*
|
||||
* This file is part of Scylla.
|
||||
*
|
||||
* Scylla is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Scylla is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Scylla. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "auth/roles-metadata.hh"
|
||||
|
||||
#include <boost/algorithm/cxx11/any_of.hpp>
|
||||
#include <seastar/core/print.hh>
|
||||
#include <seastar/core/shared_ptr.hh>
|
||||
#include <seastar/core/sstring.hh>
|
||||
|
||||
#include "auth/common.hh"
|
||||
#include "cql3/query_processor.hh"
|
||||
#include "cql3/untyped_result_set.hh"
|
||||
|
||||
namespace auth {
|
||||
|
||||
namespace meta {
|
||||
|
||||
namespace roles_table {
|
||||
|
||||
stdx::string_view creation_query() {
|
||||
static const sstring instance = sprint(
|
||||
"CREATE TABLE %s ("
|
||||
" %s text PRIMARY KEY,"
|
||||
" can_login boolean,"
|
||||
" is_superuser boolean,"
|
||||
" member_of set<text>,"
|
||||
" salted_hash text"
|
||||
")",
|
||||
qualified_name(),
|
||||
role_col_name);
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
stdx::string_view qualified_name() noexcept {
|
||||
static const sstring instance = AUTH_KS + "." + sstring(name);
|
||||
return instance;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
future<bool> default_role_row_satisfies(
|
||||
cql3::query_processor& qp,
|
||||
std::function<bool(const cql3::untyped_result_set_row&)> p) {
|
||||
static const sstring query = sprint(
|
||||
"SELECT * FROM %s WHERE %s = ?",
|
||||
meta::roles_table::qualified_name(),
|
||||
meta::roles_table::role_col_name);
|
||||
|
||||
return do_with(std::move(p), [&qp](const auto& p) {
|
||||
return qp.process(
|
||||
query,
|
||||
db::consistency_level::ONE,
|
||||
{meta::DEFAULT_SUPERUSER_NAME},
|
||||
true).then([&qp, &p](::shared_ptr<cql3::untyped_result_set> results) {
|
||||
if (results->empty()) {
|
||||
return qp.process(
|
||||
query,
|
||||
db::consistency_level::QUORUM,
|
||||
{meta::DEFAULT_SUPERUSER_NAME},
|
||||
true).then([&p](::shared_ptr<cql3::untyped_result_set> results) {
|
||||
if (results->empty()) {
|
||||
return make_ready_future<bool>(false);
|
||||
}
|
||||
|
||||
return make_ready_future<bool>(p(results->one()));
|
||||
});
|
||||
}
|
||||
|
||||
return make_ready_future<bool>(p(results->one()));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
future<bool> any_nondefault_role_row_satisfies(
|
||||
cql3::query_processor& qp,
|
||||
std::function<bool(const cql3::untyped_result_set_row&)> p) {
|
||||
static const sstring query = sprint("SELECT * FROM %s", meta::roles_table::qualified_name());
|
||||
|
||||
return do_with(std::move(p), [&qp](const auto& p) {
|
||||
return qp.process(
|
||||
query,
|
||||
db::consistency_level::QUORUM).then([&p](::shared_ptr<cql3::untyped_result_set> results) {
|
||||
if (results->empty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
static const sstring col_name = sstring(meta::roles_table::role_col_name);
|
||||
|
||||
return boost::algorithm::any_of(*results, [&p](const cql3::untyped_result_set_row& row) {
|
||||
const bool is_nondefault = row.get_as<sstring>(col_name) != meta::DEFAULT_SUPERUSER_NAME;
|
||||
return is_nondefault && p(row);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,69 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2017 ScyllaDB
|
||||
*/
|
||||
|
||||
/*
|
||||
* This file is part of Scylla.
|
||||
*
|
||||
* Scylla is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Scylla is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Scylla. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <experimental/string_view>
|
||||
#include <functional>
|
||||
|
||||
#include <seastar/core/future.hh>
|
||||
|
||||
#include "seastarx.hh"
|
||||
#include "stdx.hh"
|
||||
|
||||
namespace cql3 {
|
||||
class query_processor;
|
||||
class untyped_result_set_row;
|
||||
}
|
||||
|
||||
namespace auth {
|
||||
|
||||
namespace meta {
|
||||
|
||||
namespace roles_table {
|
||||
|
||||
stdx::string_view creation_query();
|
||||
|
||||
constexpr stdx::string_view name{"roles", 5};
|
||||
|
||||
stdx::string_view qualified_name() noexcept;
|
||||
|
||||
constexpr stdx::string_view role_col_name{"role", 4};
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
///
|
||||
/// Check that the default role satisfies a predicate, or `false` if the default role does not exist.
|
||||
///
|
||||
future<bool> default_role_row_satisfies(
|
||||
cql3::query_processor&,
|
||||
std::function<bool(const cql3::untyped_result_set_row&)>);
|
||||
|
||||
///
|
||||
/// Check that any nondefault role satisfies a predicate. `false` if no nondefault roles exist.
|
||||
///
|
||||
future<bool> any_nondefault_role_row_satisfies(
|
||||
cql3::query_processor&,
|
||||
std::function<bool(const cql3::untyped_result_set_row&)>);
|
||||
|
||||
}
|
||||
509
auth/service.cc
509
auth/service.cc
@@ -21,19 +21,14 @@
|
||||
|
||||
#include "auth/service.hh"
|
||||
|
||||
#include <algorithm>
|
||||
#include <map>
|
||||
|
||||
#include <seastar/core/future-util.hh>
|
||||
#include <seastar/core/sharded.hh>
|
||||
#include <seastar/core/shared_ptr.hh>
|
||||
|
||||
#include "auth/allow_all_authenticator.hh"
|
||||
#include "auth/allow_all_authorizer.hh"
|
||||
#include "auth/common.hh"
|
||||
#include "auth/password_authenticator.hh"
|
||||
#include "auth/role_or_anonymous.hh"
|
||||
#include "auth/standard_role_manager.hh"
|
||||
#include "cql3/query_processor.hh"
|
||||
#include "cql3/untyped_result_set.hh"
|
||||
#include "db/config.hh"
|
||||
@@ -77,18 +72,11 @@ private:
|
||||
void on_update_view(const sstring& ks_name, const sstring& view_name, bool columns_changed) override {}
|
||||
|
||||
void on_drop_keyspace(const sstring& ks_name) override {
|
||||
_authorizer.revoke_all(
|
||||
auth::make_data_resource(ks_name)).handle_exception_type([](const unsupported_authorization_operation&) {
|
||||
// Nothing.
|
||||
});
|
||||
_authorizer.revoke_all(auth::data_resource(ks_name));
|
||||
}
|
||||
|
||||
void on_drop_column_family(const sstring& ks_name, const sstring& cf_name) override {
|
||||
_authorizer.revoke_all(
|
||||
auth::make_data_resource(
|
||||
ks_name, cf_name)).handle_exception_type([](const unsupported_authorization_operation&) {
|
||||
// Nothing.
|
||||
});
|
||||
_authorizer.revoke_all(auth::data_resource(ks_name, cf_name));
|
||||
}
|
||||
|
||||
void on_drop_user_type(const sstring& ks_name, const sstring& type_name) override {}
|
||||
@@ -97,23 +85,38 @@ private:
|
||||
void on_drop_view(const sstring& ks_name, const sstring& view_name) override {}
|
||||
};
|
||||
|
||||
static future<> validate_role_exists(const service& ser, stdx::string_view role_name) {
|
||||
return ser.underlying_role_manager().exists(role_name).then([role_name](bool exists) {
|
||||
if (!exists) {
|
||||
throw nonexistant_role(role_name);
|
||||
}
|
||||
});
|
||||
static db::consistency_level consistency_for_user(const sstring& name) {
|
||||
if (name == meta::DEFAULT_SUPERUSER_NAME) {
|
||||
return db::consistency_level::QUORUM;
|
||||
} else {
|
||||
return db::consistency_level::LOCAL_ONE;
|
||||
}
|
||||
}
|
||||
|
||||
static future<::shared_ptr<cql3::untyped_result_set>> select_user(cql3::query_processor& qp, const sstring& name) {
|
||||
// Here was a thread local, explicit cache of prepared statement. In normal execution this is
|
||||
// fine, but since we in testing set up and tear down system over and over, we'd start using
|
||||
// obsolete prepared statements pretty quickly.
|
||||
// Rely on query processing caching statements instead, and lets assume
|
||||
// that a map lookup string->statement is not gonna kill us much.
|
||||
return qp.process(
|
||||
sprint(
|
||||
"SELECT * FROM %s.%s WHERE %s = ?",
|
||||
meta::AUTH_KS,
|
||||
meta::USERS_CF,
|
||||
meta::user_name_col_name),
|
||||
consistency_for_user(name),
|
||||
{ name },
|
||||
true);
|
||||
}
|
||||
|
||||
service_config service_config::from_db_config(const db::config& dc) {
|
||||
const qualified_name qualified_authorizer_name(meta::AUTH_PACKAGE_NAME, dc.authorizer());
|
||||
const qualified_name qualified_authenticator_name(meta::AUTH_PACKAGE_NAME, dc.authenticator());
|
||||
const qualified_name qualified_role_manager_name(meta::AUTH_PACKAGE_NAME, dc.role_manager());
|
||||
|
||||
service_config c;
|
||||
c.authorizer_java_name = qualified_authorizer_name;
|
||||
c.authenticator_java_name = qualified_authenticator_name;
|
||||
c.role_manager_java_name = qualified_role_manager_name;
|
||||
|
||||
return c;
|
||||
}
|
||||
@@ -122,47 +125,41 @@ service::service(
|
||||
permissions_cache_config c,
|
||||
cql3::query_processor& qp,
|
||||
::service::migration_manager& mm,
|
||||
std::unique_ptr<authorizer> z,
|
||||
std::unique_ptr<authenticator> a,
|
||||
std::unique_ptr<role_manager> r)
|
||||
std::unique_ptr<authorizer> a,
|
||||
std::unique_ptr<authenticator> b)
|
||||
: _permissions_cache_config(std::move(c))
|
||||
, _permissions_cache(nullptr)
|
||||
, _qp(qp)
|
||||
, _migration_manager(mm)
|
||||
, _authorizer(std::move(z))
|
||||
, _authenticator(std::move(a))
|
||||
, _role_manager(std::move(r))
|
||||
, _authorizer(std::move(a))
|
||||
, _authenticator(std::move(b))
|
||||
, _migration_listener(std::make_unique<auth_migration_listener>(*_authorizer)) {
|
||||
// The password authenticator requires that the `standard_role_manager` is running so that the roles metadata table
|
||||
// it manages is created and updated. This cross-module dependency is rather gross, but we have to maintain it for
|
||||
// the sake of compatibility with Apache Cassandra and its choice of auth. schema.
|
||||
if ((_authenticator->qualified_java_name() == password_authenticator_name())
|
||||
&& (_role_manager->qualified_java_name() != standard_role_manager_name())) {
|
||||
throw incompatible_module_combination(
|
||||
sprint(
|
||||
"The %s authenticator must be loaded alongside the %s role-manager.",
|
||||
password_authenticator_name(),
|
||||
standard_role_manager_name()));
|
||||
}
|
||||
}
|
||||
|
||||
service::service(
|
||||
permissions_cache_config c,
|
||||
permissions_cache_config cache_config,
|
||||
cql3::query_processor& qp,
|
||||
::service::migration_manager& mm,
|
||||
const service_config& sc)
|
||||
: service(
|
||||
std::move(c),
|
||||
std::move(cache_config),
|
||||
qp,
|
||||
mm,
|
||||
create_object<authorizer>(sc.authorizer_java_name, qp, mm),
|
||||
create_object<authenticator>(sc.authenticator_java_name, qp, mm),
|
||||
create_object<role_manager>(sc.role_manager_java_name, qp, mm)) {
|
||||
create_object<authenticator>(sc.authenticator_java_name, qp, mm)) {
|
||||
}
|
||||
|
||||
future<> service::create_keyspace_if_missing() const {
|
||||
bool service::should_create_metadata() const {
|
||||
const bool null_authorizer = _authorizer->qualified_java_name() == allow_all_authorizer_name();
|
||||
const bool null_authenticator = _authenticator->qualified_java_name() == allow_all_authenticator_name();
|
||||
return !null_authorizer || !null_authenticator;
|
||||
}
|
||||
|
||||
future<> service::create_metadata_if_missing() {
|
||||
auto& db = _qp.db().local();
|
||||
|
||||
auto f = make_ready_future<>();
|
||||
|
||||
if (!db.has_keyspace(meta::AUTH_KS)) {
|
||||
std::map<sstring, sstring> opts{{"replication_factor", "1"}};
|
||||
|
||||
@@ -174,17 +171,73 @@ future<> service::create_keyspace_if_missing() const {
|
||||
|
||||
// We use min_timestamp so that default keyspace metadata will loose with any manual adjustments.
|
||||
// See issue #2129.
|
||||
return _migration_manager.announce_new_keyspace(ksm, api::min_timestamp, false);
|
||||
f = _migration_manager.announce_new_keyspace(ksm, api::min_timestamp, false);
|
||||
}
|
||||
|
||||
return make_ready_future<>();
|
||||
return f.then([this] {
|
||||
// 3 months.
|
||||
static const auto gc_grace_seconds = 90 * 24 * 60 * 60;
|
||||
|
||||
static const sstring users_table_query = sprint(
|
||||
"CREATE TABLE %s.%s (%s text, %s boolean, PRIMARY KEY (%s)) WITH gc_grace_seconds=%s",
|
||||
meta::AUTH_KS,
|
||||
meta::USERS_CF,
|
||||
meta::user_name_col_name,
|
||||
meta::superuser_col_name,
|
||||
meta::user_name_col_name,
|
||||
gc_grace_seconds);
|
||||
|
||||
return create_metadata_table_if_missing(
|
||||
meta::USERS_CF,
|
||||
_qp,
|
||||
users_table_query,
|
||||
_migration_manager);
|
||||
}).then([this] {
|
||||
delay_until_system_ready(_delayed, [this] {
|
||||
return has_existing_users().then([this](bool existing) {
|
||||
if (!existing) {
|
||||
//
|
||||
// Create default superuser.
|
||||
//
|
||||
|
||||
static const sstring query = sprint(
|
||||
"INSERT INTO %s.%s (%s, %s) VALUES (?, ?) USING TIMESTAMP 0",
|
||||
meta::AUTH_KS,
|
||||
meta::USERS_CF,
|
||||
meta::user_name_col_name,
|
||||
meta::superuser_col_name);
|
||||
|
||||
return _qp.process(
|
||||
query,
|
||||
db::consistency_level::ONE,
|
||||
{ meta::DEFAULT_SUPERUSER_NAME, true }).then([](auto&&) {
|
||||
log.info("Created default superuser '{}'", meta::DEFAULT_SUPERUSER_NAME);
|
||||
}).handle_exception([](auto exn) {
|
||||
try {
|
||||
std::rethrow_exception(exn);
|
||||
} catch (const exceptions::request_execution_exception&) {
|
||||
log.warn("Skipped default superuser setup: some nodes were not ready");
|
||||
}
|
||||
}).discard_result();
|
||||
}
|
||||
|
||||
return make_ready_future<>();
|
||||
});
|
||||
});
|
||||
|
||||
return make_ready_future<>();
|
||||
});
|
||||
}
|
||||
|
||||
future<> service::start() {
|
||||
return once_among_shards([this] {
|
||||
return create_keyspace_if_missing();
|
||||
if (should_create_metadata()) {
|
||||
return create_metadata_if_missing();
|
||||
}
|
||||
|
||||
return make_ready_future<>();
|
||||
}).then([this] {
|
||||
return when_all_succeed(_role_manager->start(), _authorizer->start(), _authenticator->start());
|
||||
return when_all_succeed(_authorizer->start(), _authenticator->start());
|
||||
}).then([this] {
|
||||
_permissions_cache = std::make_unique<permissions_cache>(_permissions_cache_config, *this, log);
|
||||
}).then([this] {
|
||||
@@ -196,16 +249,17 @@ future<> service::start() {
|
||||
}
|
||||
|
||||
future<> service::stop() {
|
||||
return _permissions_cache->stop().then([this] {
|
||||
return when_all_succeed(_role_manager->stop(), _authorizer->stop(), _authenticator->stop());
|
||||
return once_among_shards([this] {
|
||||
_delayed.cancel_all();
|
||||
return make_ready_future<>();
|
||||
}).then([this] {
|
||||
return _permissions_cache->stop();
|
||||
}).then([this] {
|
||||
return when_all_succeed(_authorizer->stop(), _authenticator->stop());
|
||||
});
|
||||
}
|
||||
|
||||
future<bool> service::has_existing_legacy_users() const {
|
||||
if (!_qp.db().local().has_schema(meta::AUTH_KS, meta::USERS_CF)) {
|
||||
return make_ready_future<bool>(false);
|
||||
}
|
||||
|
||||
future<bool> service::has_existing_users() const {
|
||||
static const sstring default_user_query = sprint(
|
||||
"SELECT * FROM %s.%s WHERE %s = ?",
|
||||
meta::AUTH_KS,
|
||||
@@ -223,7 +277,7 @@ future<bool> service::has_existing_legacy_users() const {
|
||||
return _qp.process(
|
||||
default_user_query,
|
||||
db::consistency_level::ONE,
|
||||
{meta::DEFAULT_SUPERUSER_NAME},
|
||||
{ meta::DEFAULT_SUPERUSER_NAME },
|
||||
true).then([this](auto results) {
|
||||
if (!results->empty()) {
|
||||
return make_ready_future<bool>(true);
|
||||
@@ -232,7 +286,7 @@ future<bool> service::has_existing_legacy_users() const {
|
||||
return _qp.process(
|
||||
default_user_query,
|
||||
db::consistency_level::QUORUM,
|
||||
{meta::DEFAULT_SUPERUSER_NAME},
|
||||
{ meta::DEFAULT_SUPERUSER_NAME },
|
||||
true).then([this](auto results) {
|
||||
if (!results->empty()) {
|
||||
return make_ready_future<bool>(true);
|
||||
@@ -247,334 +301,55 @@ future<bool> service::has_existing_legacy_users() const {
|
||||
});
|
||||
}
|
||||
|
||||
future<permission_set>
|
||||
service::get_uncached_permissions(const role_or_anonymous& maybe_role, const resource& r) const {
|
||||
if (is_anonymous(maybe_role)) {
|
||||
return _authorizer->authorize(maybe_role, r);
|
||||
}
|
||||
|
||||
const stdx::string_view role_name = *maybe_role.name;
|
||||
|
||||
return has_superuser(role_name).then([this, role_name, &r](bool superuser) {
|
||||
if (superuser) {
|
||||
return make_ready_future<permission_set>(r.applicable_permissions());
|
||||
}
|
||||
|
||||
//
|
||||
// Aggregate the permissions from all granted roles.
|
||||
//
|
||||
|
||||
return do_with(permission_set(), [this, role_name, &r](auto& all_perms) {
|
||||
return get_roles(role_name).then([this, &r, &all_perms](role_set all_roles) {
|
||||
return do_with(std::move(all_roles), [this, &r, &all_perms](const auto& all_roles) {
|
||||
return parallel_for_each(all_roles, [this, &r, &all_perms](stdx::string_view role_name) {
|
||||
return _authorizer->authorize(role_name, r).then([&all_perms](permission_set perms) {
|
||||
all_perms = permission_set::from_mask(all_perms.mask() | perms.mask());
|
||||
});
|
||||
});
|
||||
});
|
||||
}).then([&all_perms] {
|
||||
return all_perms;
|
||||
});
|
||||
});
|
||||
future<bool> service::is_existing_user(const sstring& name) const {
|
||||
return select_user(_qp, name).then([](auto results) {
|
||||
return !results->empty();
|
||||
});
|
||||
}
|
||||
|
||||
future<permission_set> service::get_permissions(const role_or_anonymous& maybe_role, const resource& r) const {
|
||||
return _permissions_cache->get(maybe_role, r);
|
||||
}
|
||||
|
||||
future<bool> service::has_superuser(stdx::string_view role_name) const {
|
||||
return this->get_roles(std::move(role_name)).then([this](role_set roles) {
|
||||
return do_with(std::move(roles), [this](const role_set& roles) {
|
||||
return do_with(false, roles.begin(), [this, &roles](bool& any_super, auto& iter) {
|
||||
return do_until(
|
||||
[&roles, &any_super, &iter] { return any_super || (iter == roles.end()); },
|
||||
[this, &any_super, &iter] {
|
||||
return _role_manager->is_superuser(*iter++).then([&any_super](bool super) {
|
||||
any_super = super;
|
||||
});
|
||||
}).then([&any_super] {
|
||||
return any_super;
|
||||
});
|
||||
});
|
||||
});
|
||||
future<bool> service::is_super_user(const sstring& name) const {
|
||||
return select_user(_qp, name).then([](auto results) {
|
||||
return !results->empty() && results->one().template get_as<bool>(meta::superuser_col_name);
|
||||
});
|
||||
}
|
||||
|
||||
future<role_set> service::get_roles(stdx::string_view role_name) const {
|
||||
//
|
||||
// We may wish to cache this information in the future (as Apache Cassandra does).
|
||||
//
|
||||
|
||||
return _role_manager->query_granted(role_name, recursive_role_query::yes);
|
||||
future<> service::insert_user(const sstring& name, bool is_superuser) {
|
||||
return _qp.process(
|
||||
sprint(
|
||||
"INSERT INTO %s.%s (%s, %s) VALUES (?, ?)",
|
||||
meta::AUTH_KS,
|
||||
meta::USERS_CF,
|
||||
meta::user_name_col_name,
|
||||
meta::superuser_col_name),
|
||||
consistency_for_user(name),
|
||||
{ name, is_superuser }).discard_result();
|
||||
}
|
||||
|
||||
future<bool> service::exists(const resource& r) const {
|
||||
switch (r.kind()) {
|
||||
case resource_kind::data: {
|
||||
const auto& db = _qp.db().local();
|
||||
future<> service::delete_user(const sstring& name) {
|
||||
return _qp.process(
|
||||
sprint(
|
||||
"DELETE FROM %s.%s WHERE %s = ?",
|
||||
meta::AUTH_KS,
|
||||
meta::USERS_CF,
|
||||
meta::user_name_col_name),
|
||||
consistency_for_user(name),
|
||||
{ name }).discard_result();
|
||||
}
|
||||
|
||||
data_resource_view v(r);
|
||||
const auto keyspace = v.keyspace();
|
||||
const auto table = v.table();
|
||||
|
||||
if (table) {
|
||||
return make_ready_future<bool>(db.has_schema(sstring(*keyspace), sstring(*table)));
|
||||
}
|
||||
|
||||
if (keyspace) {
|
||||
return make_ready_future<bool>(db.has_keyspace(sstring(*keyspace)));
|
||||
}
|
||||
|
||||
return make_ready_future<bool>(true);
|
||||
}
|
||||
|
||||
case resource_kind::role: {
|
||||
role_resource_view v(r);
|
||||
const auto role = v.role();
|
||||
|
||||
if (role) {
|
||||
return _role_manager->exists(*role);
|
||||
}
|
||||
|
||||
return make_ready_future<bool>(true);
|
||||
}
|
||||
}
|
||||
|
||||
return make_ready_future<bool>(false);
|
||||
future<permission_set> service::get_permissions(::shared_ptr<authenticated_user> u, data_resource r) const {
|
||||
return _permissions_cache->get(std::move(u), std::move(r));
|
||||
}
|
||||
|
||||
//
|
||||
// Free functions.
|
||||
//
|
||||
|
||||
future<bool> has_superuser(const service& ser, const authenticated_user& u) {
|
||||
if (is_anonymous(u)) {
|
||||
future<bool> is_super_user(const service& ser, const authenticated_user& u) {
|
||||
if (u.is_anonymous()) {
|
||||
return make_ready_future<bool>(false);
|
||||
}
|
||||
|
||||
return ser.has_superuser(*u.name);
|
||||
}
|
||||
|
||||
future<role_set> get_roles(const service& ser, const authenticated_user& u) {
|
||||
if (is_anonymous(u)) {
|
||||
return make_ready_future<role_set>();
|
||||
}
|
||||
|
||||
return ser.get_roles(*u.name);
|
||||
}
|
||||
|
||||
future<permission_set> get_permissions(const service& ser, const authenticated_user& u, const resource& r) {
|
||||
return do_with(role_or_anonymous(), [&ser, &u, &r](auto& maybe_role) {
|
||||
maybe_role.name = u.name;
|
||||
return ser.get_permissions(maybe_role, r);
|
||||
});
|
||||
}
|
||||
|
||||
bool is_enforcing(const service& ser) {
|
||||
const bool enforcing_authorizer = ser.underlying_authorizer().qualified_java_name() != allow_all_authorizer_name();
|
||||
|
||||
const bool enforcing_authenticator = ser.underlying_authenticator().qualified_java_name()
|
||||
!= allow_all_authenticator_name();
|
||||
|
||||
return enforcing_authorizer || enforcing_authenticator;
|
||||
}
|
||||
|
||||
bool is_protected(const service& ser, const resource& r) noexcept {
|
||||
return ser.underlying_role_manager().protected_resources().count(r)
|
||||
|| ser.underlying_authenticator().protected_resources().count(r)
|
||||
|| ser.underlying_authorizer().protected_resources().count(r);
|
||||
}
|
||||
|
||||
static void validate_authentication_options_are_supported(
|
||||
const authentication_options& options,
|
||||
const authentication_option_set& supported) {
|
||||
const auto check = [&supported](authentication_option k) {
|
||||
if (supported.count(k) == 0) {
|
||||
throw unsupported_authentication_option(k);
|
||||
}
|
||||
};
|
||||
|
||||
if (options.password) {
|
||||
check(authentication_option::password);
|
||||
}
|
||||
|
||||
if (options.options) {
|
||||
check(authentication_option::options);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
future<> create_role(
|
||||
const service& ser,
|
||||
stdx::string_view name,
|
||||
const role_config& config,
|
||||
const authentication_options& options) {
|
||||
return ser.underlying_role_manager().create(name, config).then([&ser, name, &options] {
|
||||
if (!auth::any_authentication_options(options)) {
|
||||
return make_ready_future<>();
|
||||
}
|
||||
|
||||
return futurize_apply(
|
||||
&validate_authentication_options_are_supported,
|
||||
options,
|
||||
ser.underlying_authenticator().supported_options()).then([&ser, name, &options] {
|
||||
return ser.underlying_authenticator().create(name, options);
|
||||
}).handle_exception([&ser, &name](std::exception_ptr ep) {
|
||||
// Roll-back.
|
||||
return ser.underlying_role_manager().drop(name).then([ep = std::move(ep)] {
|
||||
std::rethrow_exception(ep);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
future<> alter_role(
|
||||
const service& ser,
|
||||
stdx::string_view name,
|
||||
const role_config_update& config_update,
|
||||
const authentication_options& options) {
|
||||
return ser.underlying_role_manager().alter(name, config_update).then([&ser, name, &options] {
|
||||
if (!any_authentication_options(options)) {
|
||||
return make_ready_future<>();
|
||||
}
|
||||
|
||||
return futurize_apply(
|
||||
&validate_authentication_options_are_supported,
|
||||
options,
|
||||
ser.underlying_authenticator().supported_options()).then([&ser, name, &options] {
|
||||
return ser.underlying_authenticator().alter(name, options);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
future<> drop_role(const service& ser, stdx::string_view name) {
|
||||
return do_with(make_role_resource(name), [&ser, name](const resource& r) {
|
||||
auto& a = ser.underlying_authorizer();
|
||||
|
||||
return when_all_succeed(
|
||||
a.revoke_all(name),
|
||||
a.revoke_all(r)).handle_exception_type([](const unsupported_authorization_operation&) {
|
||||
// Nothing.
|
||||
});
|
||||
}).then([&ser, name] {
|
||||
return ser.underlying_authenticator().drop(name);
|
||||
}).then([&ser, name] {
|
||||
return ser.underlying_role_manager().drop(name);
|
||||
});
|
||||
}
|
||||
|
||||
future<bool> has_role(const service& ser, stdx::string_view grantee, stdx::string_view name) {
|
||||
return when_all_succeed(
|
||||
validate_role_exists(ser, name),
|
||||
ser.get_roles(grantee)).then([name](role_set all_roles) {
|
||||
return make_ready_future<bool>(all_roles.count(sstring(name)) != 0);
|
||||
});
|
||||
}
|
||||
future<bool> has_role(const service& ser, const authenticated_user& u, stdx::string_view name) {
|
||||
if (is_anonymous(u)) {
|
||||
return make_ready_future<bool>(false);
|
||||
}
|
||||
|
||||
return has_role(ser, *u.name, name);
|
||||
}
|
||||
|
||||
future<> grant_permissions(
|
||||
const service& ser,
|
||||
stdx::string_view role_name,
|
||||
permission_set perms,
|
||||
const resource& r) {
|
||||
return validate_role_exists(ser, role_name).then([&ser, role_name, perms, &r] {
|
||||
return ser.underlying_authorizer().grant(role_name, perms, r);
|
||||
});
|
||||
}
|
||||
|
||||
future<> grant_applicable_permissions(const service& ser, stdx::string_view role_name, const resource& r) {
|
||||
return grant_permissions(ser, role_name, r.applicable_permissions(), r);
|
||||
}
|
||||
future<> grant_applicable_permissions(const service& ser, const authenticated_user& u, const resource& r) {
|
||||
if (is_anonymous(u)) {
|
||||
return make_ready_future<>();
|
||||
}
|
||||
|
||||
return grant_applicable_permissions(ser, *u.name, r);
|
||||
}
|
||||
|
||||
future<> revoke_permissions(
|
||||
const service& ser,
|
||||
stdx::string_view role_name,
|
||||
permission_set perms,
|
||||
const resource& r) {
|
||||
return validate_role_exists(ser, role_name).then([&ser, role_name, perms, &r] {
|
||||
return ser.underlying_authorizer().revoke(role_name, perms, r);
|
||||
});
|
||||
}
|
||||
|
||||
future<std::vector<permission_details>> list_filtered_permissions(
|
||||
const service& ser,
|
||||
permission_set perms,
|
||||
std::optional<stdx::string_view> role_name,
|
||||
const std::optional<std::pair<resource, recursive_permissions>>& resource_filter) {
|
||||
return ser.underlying_authorizer().list_all().then([&ser, perms, role_name, &resource_filter](
|
||||
std::vector<permission_details> all_details) {
|
||||
|
||||
if (resource_filter) {
|
||||
const resource r = resource_filter->first;
|
||||
|
||||
const auto resources = resource_filter->second
|
||||
? auth::expand_resource_family(r)
|
||||
: auth::resource_set{r};
|
||||
|
||||
all_details.erase(
|
||||
std::remove_if(
|
||||
all_details.begin(),
|
||||
all_details.end(),
|
||||
[&resources](const permission_details& pd) {
|
||||
return resources.count(pd.resource) == 0;
|
||||
}),
|
||||
all_details.end());
|
||||
}
|
||||
|
||||
std::transform(
|
||||
std::make_move_iterator(all_details.begin()),
|
||||
std::make_move_iterator(all_details.end()),
|
||||
all_details.begin(),
|
||||
[perms](permission_details pd) {
|
||||
pd.permissions = permission_set::from_mask(pd.permissions.mask() & perms.mask());
|
||||
return pd;
|
||||
});
|
||||
|
||||
// Eliminate rows with an empty permission set.
|
||||
all_details.erase(
|
||||
std::remove_if(all_details.begin(), all_details.end(), [](const permission_details& pd) {
|
||||
return pd.permissions.mask() == 0;
|
||||
}),
|
||||
all_details.end());
|
||||
|
||||
if (!role_name) {
|
||||
return make_ready_future<std::vector<permission_details>>(std::move(all_details));
|
||||
}
|
||||
|
||||
//
|
||||
// Filter out rows based on whether permissions have been granted to this role (directly or indirectly).
|
||||
//
|
||||
|
||||
return do_with(std::move(all_details), [&ser, role_name](auto& all_details) {
|
||||
return ser.get_roles(*role_name).then([&all_details](role_set all_roles) {
|
||||
all_details.erase(
|
||||
std::remove_if(
|
||||
all_details.begin(),
|
||||
all_details.end(),
|
||||
[&all_roles](const permission_details& pd) {
|
||||
return all_roles.count(pd.role_name) == 0;
|
||||
}),
|
||||
all_details.end());
|
||||
|
||||
return make_ready_future<std::vector<permission_details>>(std::move(all_details));
|
||||
});
|
||||
});
|
||||
});
|
||||
return ser.is_super_user(u.name());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
213
auth/service.hh
213
auth/service.hh
@@ -21,21 +21,18 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <experimental/string_view>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
|
||||
#include <seastar/core/future.hh>
|
||||
#include <seastar/core/sstring.hh>
|
||||
#include <seastar/util/bool_class.hh>
|
||||
|
||||
#include "auth/authenticator.hh"
|
||||
#include "auth/authorizer.hh"
|
||||
#include "auth/authenticated_user.hh"
|
||||
#include "auth/permission.hh"
|
||||
#include "auth/permissions_cache.hh"
|
||||
#include "auth/role_manager.hh"
|
||||
#include "delayed_tasks.hh"
|
||||
#include "seastarx.hh"
|
||||
#include "stdx.hh"
|
||||
|
||||
namespace cql3 {
|
||||
class query_processor;
|
||||
@@ -52,37 +49,16 @@ class migration_listener;
|
||||
|
||||
namespace auth {
|
||||
|
||||
class role_or_anonymous;
|
||||
class authenticator;
|
||||
class authorizer;
|
||||
|
||||
struct service_config final {
|
||||
static service_config from_db_config(const db::config&);
|
||||
|
||||
sstring authorizer_java_name;
|
||||
sstring authenticator_java_name;
|
||||
sstring role_manager_java_name;
|
||||
};
|
||||
|
||||
///
|
||||
/// Due to poor (in this author's opinion) decisions of Apache Cassandra, certain choices of one role-manager,
|
||||
/// authenticator, or authorizer imply restrictions on the rest.
|
||||
///
|
||||
/// This exception is thrown when an invalid combination of modules is selected, with a message explaining the
|
||||
/// incompatibility.
|
||||
///
|
||||
class incompatible_module_combination : public std::invalid_argument {
|
||||
public:
|
||||
using std::invalid_argument::invalid_argument;
|
||||
};
|
||||
|
||||
///
|
||||
/// Client for access-control in the system.
|
||||
///
|
||||
/// Access control encompasses user/role management, authentication, and authorization. This client provides access to
|
||||
/// the dynamically-loaded implementations of these modules (through the `underlying_*` member functions), but also
|
||||
/// builds on their functionality with caching and abstractions for common operations.
|
||||
///
|
||||
/// All state associated with access-control is stored externally to any particular instance of this class.
|
||||
///
|
||||
class service final {
|
||||
permissions_cache_config _permissions_cache_config;
|
||||
std::unique_ptr<permissions_cache> _permissions_cache;
|
||||
@@ -95,25 +71,19 @@ class service final {
|
||||
|
||||
std::unique_ptr<authenticator> _authenticator;
|
||||
|
||||
std::unique_ptr<role_manager> _role_manager;
|
||||
|
||||
// Only one of these should be registered, so we end up with some unused instances. Not the end of the world.
|
||||
std::unique_ptr<::service::migration_listener> _migration_listener;
|
||||
|
||||
delayed_tasks<> _delayed{};
|
||||
|
||||
public:
|
||||
service(
|
||||
permissions_cache_config,
|
||||
cql3::query_processor&,
|
||||
::service::migration_manager&,
|
||||
std::unique_ptr<authorizer>,
|
||||
std::unique_ptr<authenticator>,
|
||||
std::unique_ptr<role_manager>);
|
||||
std::unique_ptr<authenticator>);
|
||||
|
||||
///
|
||||
/// This constructor is intended to be used when the class is sharded via \ref seastar::sharded. In that case, the
|
||||
/// arguments must be copyable, which is why we delay construction with instance-construction instructions instead
|
||||
/// of the instances themselves.
|
||||
///
|
||||
service(
|
||||
permissions_cache_config,
|
||||
cql3::query_processor&,
|
||||
@@ -124,173 +94,40 @@ public:
|
||||
|
||||
future<> stop();
|
||||
|
||||
///
|
||||
/// \returns an exceptional future with \ref nonexistant_role if the named role does not exist.
|
||||
///
|
||||
future<permission_set> get_permissions(const role_or_anonymous&, const resource&) const;
|
||||
future<bool> is_existing_user(const sstring& name) const;
|
||||
|
||||
///
|
||||
/// Like \ref get_permissions, but never returns cached permissions.
|
||||
///
|
||||
future<permission_set> get_uncached_permissions(const role_or_anonymous&, const resource&) const;
|
||||
future<bool> is_super_user(const sstring& name) const;
|
||||
|
||||
///
|
||||
/// Query whether the named role has been granted a role that is a superuser.
|
||||
///
|
||||
/// A role is always granted to itself. Therefore, a role that "is" a superuser also "has" superuser.
|
||||
///
|
||||
/// \returns an exceptional future with \ref nonexistant_role if the role does not exist.
|
||||
///
|
||||
future<bool> has_superuser(stdx::string_view role_name) const;
|
||||
future<> insert_user(const sstring& name, bool is_superuser);
|
||||
|
||||
///
|
||||
/// Return the set of all roles granted to the given role, including itself and roles granted through other roles.
|
||||
///
|
||||
/// \returns an exceptional future with \ref nonexistent_role if the role does not exist.
|
||||
future<role_set> get_roles(stdx::string_view role_name) const;
|
||||
future<> delete_user(const sstring& name);
|
||||
|
||||
future<bool> exists(const resource&) const;
|
||||
future<permission_set> get_permissions(::shared_ptr<authenticated_user>, data_resource) const;
|
||||
|
||||
authenticator& underlying_authenticator() {
|
||||
return *_authenticator;
|
||||
}
|
||||
|
||||
const authenticator& underlying_authenticator() const {
|
||||
return *_authenticator;
|
||||
}
|
||||
|
||||
authorizer& underlying_authorizer() {
|
||||
return *_authorizer;
|
||||
}
|
||||
|
||||
const authorizer& underlying_authorizer() const {
|
||||
return *_authorizer;
|
||||
}
|
||||
|
||||
const role_manager& underlying_role_manager() const {
|
||||
return *_role_manager;
|
||||
}
|
||||
|
||||
private:
|
||||
future<bool> has_existing_legacy_users() const;
|
||||
future<bool> has_existing_users() const;
|
||||
|
||||
future<> create_keyspace_if_missing() const;
|
||||
bool should_create_metadata() const;
|
||||
|
||||
future<> create_metadata_if_missing();
|
||||
};
|
||||
|
||||
future<bool> has_superuser(const service&, const authenticated_user&);
|
||||
|
||||
future<role_set> get_roles(const service&, const authenticated_user&);
|
||||
|
||||
future<permission_set> get_permissions(const service&, const authenticated_user&, const resource&);
|
||||
|
||||
///
|
||||
/// Access-control is "enforcing" when either the authenticator or the authorizer are not their "allow-all" variants.
|
||||
///
|
||||
/// Put differently, when access control is not enforcing, all operations on resources will be allowed and users do not
|
||||
/// need to authenticate themselves.
|
||||
///
|
||||
bool is_enforcing(const service&);
|
||||
|
||||
///
|
||||
/// Protected resources cannot be modified even if the performer has permissions to do so.
|
||||
///
|
||||
bool is_protected(const service&, const resource&) noexcept;
|
||||
|
||||
///
|
||||
/// Create a role with optional authentication information.
|
||||
///
|
||||
/// \returns an exceptional future with \ref role_already_exists if the user or role exists.
|
||||
///
|
||||
/// \returns an exceptional future with \ref unsupported_authentication_option if an unsupported option is included.
|
||||
///
|
||||
future<> create_role(
|
||||
const service&,
|
||||
stdx::string_view name,
|
||||
const role_config&,
|
||||
const authentication_options&);
|
||||
|
||||
///
|
||||
/// Alter an existing role and its authentication information.
|
||||
///
|
||||
/// \returns an exceptional future with \ref nonexistant_role if the named role does not exist.
|
||||
///
|
||||
/// \returns an exceptional future with \ref unsupported_authentication_option if an unsupported option is included.
|
||||
///
|
||||
future<> alter_role(
|
||||
const service&,
|
||||
stdx::string_view name,
|
||||
const role_config_update&,
|
||||
const authentication_options&);
|
||||
|
||||
///
|
||||
/// Drop a role from the system, including all permissions and authentication information.
|
||||
///
|
||||
/// \returns an exceptional future with \ref nonexistant_role if the named role does not exist.
|
||||
///
|
||||
future<> drop_role(const service&, stdx::string_view name);
|
||||
|
||||
///
|
||||
/// Check if `grantee` has been granted the named role.
|
||||
///
|
||||
/// \returns an exceptional future with \ref nonexistent_role if `grantee` or `name` do not exist.
|
||||
///
|
||||
future<bool> has_role(const service&, stdx::string_view grantee, stdx::string_view name);
|
||||
///
|
||||
/// Check if the authenticated user has been granted the named role.
|
||||
///
|
||||
/// \returns an exceptional future with \ref nonexistent_role if the user or `name` do not exist.
|
||||
///
|
||||
future<bool> has_role(const service&, const authenticated_user&, stdx::string_view name);
|
||||
|
||||
///
|
||||
/// \returns an exceptional future with \ref nonexistent_role if the named role does not exist.
|
||||
///
|
||||
/// \returns an exceptional future with \ref unsupported_authorization_operation if granting permissions is not
|
||||
/// supported.
|
||||
///
|
||||
future<> grant_permissions(
|
||||
const service&,
|
||||
stdx::string_view role_name,
|
||||
permission_set,
|
||||
const resource&);
|
||||
|
||||
///
|
||||
/// Like \ref grant_permissions, but grants all applicable permissions on the resource.
|
||||
///
|
||||
/// \returns an exceptional future with \ref nonexistent_role if the named role does not exist.
|
||||
///
|
||||
/// \returns an exceptional future with \ref unsupported_authorization_operation if granting permissions is not
|
||||
/// supported.
|
||||
///
|
||||
future<> grant_applicable_permissions(const service&, stdx::string_view role_name, const resource&);
|
||||
future<> grant_applicable_permissions(const service&, const authenticated_user&, const resource&);
|
||||
|
||||
///
|
||||
/// \returns an exceptional future with \ref nonexistent_role if the named role does not exist.
|
||||
///
|
||||
/// \returns an exceptional future with \ref unsupported_authorization_operation if revoking permissions is not
|
||||
/// supported.
|
||||
///
|
||||
future<> revoke_permissions(
|
||||
const service&,
|
||||
stdx::string_view role_name,
|
||||
permission_set,
|
||||
const resource&);
|
||||
|
||||
using recursive_permissions = bool_class<struct recursive_permissions_tag>;
|
||||
|
||||
///
|
||||
/// Query for all granted permissions according to filtering criteria.
|
||||
///
|
||||
/// Only permissions included in the provided set are included.
|
||||
///
|
||||
/// If a role name is provided, only permissions granted (directly or recursively) to the role are included.
|
||||
///
|
||||
/// If a resource filter is provided, only permissions granted on the resource are included. When \ref
|
||||
/// recursive_permissions is `true`, permissions on a parent resource are included.
|
||||
///
|
||||
/// \returns an exceptional future with \ref nonexistent_role if a role name is included which refers to a role that
|
||||
/// does not exist.
|
||||
///
|
||||
/// \returns an exceptional future with \ref unsupported_authorization_operation if listing permissions is not
|
||||
/// supported.
|
||||
///
|
||||
future<std::vector<permission_details>> list_filtered_permissions(
|
||||
const service&,
|
||||
permission_set,
|
||||
std::optional<stdx::string_view> role_name,
|
||||
const std::optional<std::pair<resource, recursive_permissions>>& resource_filter);
|
||||
future<bool> is_super_user(const service&, const authenticated_user&);
|
||||
|
||||
}
|
||||
|
||||
@@ -1,542 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2017 ScyllaDB
|
||||
*/
|
||||
|
||||
/*
|
||||
* This file is part of Scylla.
|
||||
*
|
||||
* Scylla is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Scylla is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Scylla. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "auth/standard_role_manager.hh"
|
||||
|
||||
#include <experimental/optional>
|
||||
#include <unordered_set>
|
||||
#include <vector>
|
||||
|
||||
#include <boost/algorithm/string/join.hpp>
|
||||
#include <seastar/core/future-util.hh>
|
||||
#include <seastar/core/print.hh>
|
||||
#include <seastar/core/sleep.hh>
|
||||
#include <seastar/core/sstring.hh>
|
||||
#include <seastar/core/thread.hh>
|
||||
|
||||
#include "auth/common.hh"
|
||||
#include "auth/roles-metadata.hh"
|
||||
#include "cql3/query_processor.hh"
|
||||
#include "db/consistency_level_type.hh"
|
||||
#include "exceptions/exceptions.hh"
|
||||
#include "log.hh"
|
||||
#include "utils/class_registrator.hh"
|
||||
|
||||
namespace auth {
|
||||
|
||||
namespace meta {
|
||||
|
||||
namespace role_members_table {
|
||||
|
||||
constexpr stdx::string_view name{"role_members" , 12};
|
||||
|
||||
static stdx::string_view qualified_name() noexcept {
|
||||
static const sstring instance = AUTH_KS + "." + sstring(name);
|
||||
return instance;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static logging::logger log("standard_role_manager");
|
||||
|
||||
static const class_registrator<
|
||||
role_manager,
|
||||
standard_role_manager,
|
||||
cql3::query_processor&,
|
||||
::service::migration_manager&> registration("org.apache.cassandra.auth.CassandraRoleManager");
|
||||
|
||||
struct record final {
|
||||
sstring name;
|
||||
bool is_superuser;
|
||||
bool can_login;
|
||||
role_set member_of;
|
||||
};
|
||||
|
||||
static db::consistency_level consistency_for_role(stdx::string_view role_name) noexcept {
|
||||
if (role_name == meta::DEFAULT_SUPERUSER_NAME) {
|
||||
return db::consistency_level::QUORUM;
|
||||
}
|
||||
|
||||
return db::consistency_level::LOCAL_ONE;
|
||||
}
|
||||
|
||||
static future<stdx::optional<record>> find_record(cql3::query_processor& qp, stdx::string_view role_name) {
|
||||
static const sstring query = sprint(
|
||||
"SELECT * FROM %s WHERE %s = ?",
|
||||
meta::roles_table::qualified_name(),
|
||||
meta::roles_table::role_col_name);
|
||||
|
||||
return qp.process(
|
||||
query,
|
||||
consistency_for_role(role_name),
|
||||
{sstring(role_name)},
|
||||
true).then([](::shared_ptr<cql3::untyped_result_set> results) {
|
||||
if (results->empty()) {
|
||||
return stdx::optional<record>();
|
||||
}
|
||||
|
||||
const cql3::untyped_result_set_row& row = results->one();
|
||||
|
||||
return stdx::make_optional(
|
||||
record{
|
||||
row.get_as<sstring>(sstring(meta::roles_table::role_col_name)),
|
||||
row.get_as<bool>("is_superuser"),
|
||||
row.get_as<bool>("can_login"),
|
||||
(row.has("member_of")
|
||||
? row.get_set<sstring>("member_of")
|
||||
: role_set())});
|
||||
});
|
||||
}
|
||||
|
||||
static future<record> require_record(cql3::query_processor& qp, stdx::string_view role_name) {
|
||||
return find_record(qp, role_name).then([role_name](stdx::optional<record> mr) {
|
||||
if (!mr) {
|
||||
throw nonexistant_role(role_name);
|
||||
}
|
||||
|
||||
return make_ready_future<record>(*mr);
|
||||
});
|
||||
}
|
||||
|
||||
static bool has_can_login(const cql3::untyped_result_set_row& row) {
|
||||
return row.has("can_login") && !(boolean_type->deserialize(row.get_blob("can_login")).is_null());
|
||||
}
|
||||
|
||||
stdx::string_view standard_role_manager_name() noexcept {
|
||||
static const sstring instance = meta::AUTH_PACKAGE_NAME + "CassandraRoleManager";
|
||||
return instance;
|
||||
}
|
||||
|
||||
stdx::string_view standard_role_manager::qualified_java_name() const noexcept {
|
||||
return standard_role_manager_name();
|
||||
}
|
||||
|
||||
const resource_set& standard_role_manager::protected_resources() const {
|
||||
static const resource_set resources({
|
||||
make_data_resource(meta::AUTH_KS, meta::roles_table::name),
|
||||
make_data_resource(meta::AUTH_KS, meta::role_members_table::name)});
|
||||
|
||||
return resources;
|
||||
}
|
||||
|
||||
future<> standard_role_manager::create_metadata_tables_if_missing() const {
|
||||
static const sstring create_role_members_query = sprint(
|
||||
"CREATE TABLE %s ("
|
||||
" role text,"
|
||||
" member text,"
|
||||
" PRIMARY KEY (role, member)"
|
||||
")",
|
||||
meta::role_members_table::qualified_name());
|
||||
|
||||
|
||||
return when_all_succeed(
|
||||
create_metadata_table_if_missing(
|
||||
meta::roles_table::name,
|
||||
_qp,
|
||||
meta::roles_table::creation_query(),
|
||||
_migration_manager),
|
||||
create_metadata_table_if_missing(
|
||||
meta::role_members_table::name,
|
||||
_qp,
|
||||
create_role_members_query,
|
||||
_migration_manager));
|
||||
}
|
||||
|
||||
future<> standard_role_manager::create_default_role_if_missing() const {
|
||||
return default_role_row_satisfies(_qp, &has_can_login).then([this](bool exists) {
|
||||
if (!exists) {
|
||||
static const sstring query = sprint(
|
||||
"INSERT INTO %s (%s, is_superuser, can_login) VALUES (?, true, true)",
|
||||
meta::roles_table::qualified_name(),
|
||||
meta::roles_table::role_col_name);
|
||||
|
||||
return _qp.process(
|
||||
query,
|
||||
db::consistency_level::QUORUM,
|
||||
{meta::DEFAULT_SUPERUSER_NAME}).then([](auto&&) {
|
||||
log.info("Created default superuser role '{}'.", meta::DEFAULT_SUPERUSER_NAME);
|
||||
return make_ready_future<>();
|
||||
});
|
||||
}
|
||||
|
||||
return make_ready_future<>();
|
||||
}).handle_exception_type([](const exceptions::unavailable_exception& e) {
|
||||
log.warn("Skipped default role setup: some nodes were not ready; will retry");
|
||||
return make_exception_future<>(e);
|
||||
});
|
||||
}
|
||||
|
||||
static const sstring legacy_table_name{"users"};
|
||||
|
||||
bool standard_role_manager::legacy_metadata_exists() const {
|
||||
return _qp.db().local().has_schema(meta::AUTH_KS, legacy_table_name);
|
||||
}
|
||||
|
||||
future<> standard_role_manager::migrate_legacy_metadata() const {
|
||||
log.info("Starting migration of legacy user metadata.");
|
||||
static const sstring query = sprint("SELECT * FROM %s.%s", meta::AUTH_KS, legacy_table_name);
|
||||
|
||||
return _qp.process(
|
||||
query,
|
||||
db::consistency_level::QUORUM).then([this](::shared_ptr<cql3::untyped_result_set> results) {
|
||||
return do_for_each(*results, [this](const cql3::untyped_result_set_row& row) {
|
||||
role_config config;
|
||||
config.is_superuser = row.get_as<bool>("super");
|
||||
config.can_login = true;
|
||||
|
||||
return do_with(
|
||||
row.get_as<sstring>("name"),
|
||||
std::move(config),
|
||||
[this](const auto& name, const auto& config) {
|
||||
return this->create_or_replace(name, config);
|
||||
});
|
||||
}).finally([results] {});
|
||||
}).then([] {
|
||||
log.info("Finished migrating legacy user metadata.");
|
||||
}).handle_exception([](std::exception_ptr ep) {
|
||||
log.error("Encountered an error during migration!");
|
||||
std::rethrow_exception(ep);
|
||||
});
|
||||
}
|
||||
|
||||
future<> standard_role_manager::start() {
|
||||
return once_among_shards([this] {
|
||||
return this->create_metadata_tables_if_missing().then([this] {
|
||||
_stopped = auth::do_after_system_ready(_as, [this] {
|
||||
return seastar::async([this] {
|
||||
wait_for_schema_agreement(_migration_manager, _qp.db().local()).get0();
|
||||
|
||||
if (any_nondefault_role_row_satisfies(_qp, &has_can_login).get0()) {
|
||||
if (this->legacy_metadata_exists()) {
|
||||
log.warn("Ignoring legacy user metadata since nondefault roles already exist.");
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (this->legacy_metadata_exists()) {
|
||||
this->migrate_legacy_metadata().get0();
|
||||
return;
|
||||
}
|
||||
|
||||
create_default_role_if_missing().get0();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
future<> standard_role_manager::stop() {
|
||||
_as.request_abort();
|
||||
return _stopped.handle_exception_type([] (const sleep_aborted&) { });
|
||||
}
|
||||
|
||||
future<> standard_role_manager::create_or_replace(stdx::string_view role_name, const role_config& c) const {
|
||||
static const sstring query = sprint(
|
||||
"INSERT INTO %s (%s, is_superuser, can_login) VALUES (?, ?, ?)",
|
||||
meta::roles_table::qualified_name(),
|
||||
meta::roles_table::role_col_name);
|
||||
|
||||
return _qp.process(
|
||||
query,
|
||||
consistency_for_role(role_name),
|
||||
{sstring(role_name), c.is_superuser, c.can_login},
|
||||
true).discard_result();
|
||||
}
|
||||
|
||||
future<>
|
||||
standard_role_manager::create(stdx::string_view role_name, const role_config& c) const {
|
||||
return this->exists(role_name).then([this, role_name, &c](bool role_exists) {
|
||||
if (role_exists) {
|
||||
throw role_already_exists(role_name);
|
||||
}
|
||||
|
||||
return this->create_or_replace(role_name, c);
|
||||
});
|
||||
}
|
||||
|
||||
future<>
|
||||
standard_role_manager::alter(stdx::string_view role_name, const role_config_update& u) const {
|
||||
static const auto build_column_assignments = [](const role_config_update& u) -> sstring {
|
||||
std::vector<sstring> assignments;
|
||||
|
||||
if (u.is_superuser) {
|
||||
assignments.push_back(sstring("is_superuser = ") + (*u.is_superuser ? "true" : "false"));
|
||||
}
|
||||
|
||||
if (u.can_login) {
|
||||
assignments.push_back(sstring("can_login = ") + (*u.can_login ? "true" : "false"));
|
||||
}
|
||||
|
||||
return boost::algorithm::join(assignments, ", ");
|
||||
};
|
||||
|
||||
return require_record(_qp, role_name).then([this, role_name, &u](record) {
|
||||
if (!u.is_superuser && !u.can_login) {
|
||||
return make_ready_future<>();
|
||||
}
|
||||
|
||||
return _qp.process(
|
||||
sprint(
|
||||
"UPDATE %s SET %s WHERE %s = ?",
|
||||
meta::roles_table::qualified_name(),
|
||||
build_column_assignments(u),
|
||||
meta::roles_table::role_col_name),
|
||||
consistency_for_role(role_name),
|
||||
{sstring(role_name)}).discard_result();
|
||||
});
|
||||
}
|
||||
|
||||
future<> standard_role_manager::drop(stdx::string_view role_name) const {
|
||||
return this->exists(role_name).then([this, role_name](bool role_exists) {
|
||||
if (!role_exists) {
|
||||
throw nonexistant_role(role_name);
|
||||
}
|
||||
|
||||
// First, revoke this role from all roles that are members of it.
|
||||
const auto revoke_from_members = [this, role_name] {
|
||||
static const sstring query = sprint(
|
||||
"SELECT member FROM %s WHERE role = ?",
|
||||
meta::role_members_table::qualified_name());
|
||||
|
||||
return _qp.process(
|
||||
query,
|
||||
consistency_for_role(role_name),
|
||||
{sstring(role_name)}).then([this, role_name](::shared_ptr<cql3::untyped_result_set> members) {
|
||||
return parallel_for_each(
|
||||
members->begin(),
|
||||
members->end(),
|
||||
[this, role_name](const cql3::untyped_result_set_row& member_row) {
|
||||
const sstring member = member_row.template get_as<sstring>("member");
|
||||
return this->modify_membership(member, role_name, membership_change::remove);
|
||||
}).finally([members] {});
|
||||
});
|
||||
};
|
||||
|
||||
// In parallel, revoke all roles that this role is members of.
|
||||
const auto revoke_members_of = [this, grantee = role_name] {
|
||||
return this->query_granted(
|
||||
grantee,
|
||||
recursive_role_query::no).then([this, grantee](role_set granted_roles) {
|
||||
return do_with(
|
||||
std::move(granted_roles),
|
||||
[this, grantee](const role_set& granted_roles) {
|
||||
return parallel_for_each(
|
||||
granted_roles.begin(),
|
||||
granted_roles.end(),
|
||||
[this, grantee](const sstring& role_name) {
|
||||
return this->modify_membership(grantee, role_name, membership_change::remove);
|
||||
});
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
// Finally, delete the role itself.
|
||||
auto delete_role = [this, role_name] {
|
||||
static const sstring query = sprint(
|
||||
"DELETE FROM %s WHERE %s = ?",
|
||||
meta::roles_table::qualified_name(),
|
||||
meta::roles_table::role_col_name);
|
||||
|
||||
return _qp.process(
|
||||
query,
|
||||
consistency_for_role(role_name),
|
||||
{sstring(role_name)}).discard_result();
|
||||
};
|
||||
|
||||
return when_all_succeed(revoke_from_members(), revoke_members_of()).then([delete_role = std::move(delete_role)] {
|
||||
return delete_role();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
future<>
|
||||
standard_role_manager::modify_membership(
|
||||
stdx::string_view grantee_name,
|
||||
stdx::string_view role_name,
|
||||
membership_change ch) const {
|
||||
|
||||
|
||||
const auto modify_roles = [this, role_name, grantee_name, ch] {
|
||||
const auto query = sprint(
|
||||
"UPDATE %s SET member_of = member_of %s ? WHERE %s = ?",
|
||||
meta::roles_table::qualified_name(),
|
||||
(ch == membership_change::add ? '+' : '-'),
|
||||
meta::roles_table::role_col_name);
|
||||
|
||||
return _qp.process(
|
||||
query,
|
||||
consistency_for_role(grantee_name),
|
||||
{role_set{sstring(role_name)}, sstring(grantee_name)}).discard_result();
|
||||
};
|
||||
|
||||
const auto modify_role_members = [this, role_name, grantee_name, ch] {
|
||||
switch (ch) {
|
||||
case membership_change::add:
|
||||
return _qp.process(
|
||||
sprint(
|
||||
"INSERT INTO %s (role, member) VALUES (?, ?)",
|
||||
meta::role_members_table::qualified_name()),
|
||||
consistency_for_role(role_name),
|
||||
{sstring(role_name), sstring(grantee_name)}).discard_result();
|
||||
|
||||
case membership_change::remove:
|
||||
return _qp.process(
|
||||
sprint(
|
||||
"DELETE FROM %s WHERE role = ? AND member = ?",
|
||||
meta::role_members_table::qualified_name()),
|
||||
consistency_for_role(role_name),
|
||||
{sstring(role_name), sstring(grantee_name)}).discard_result();
|
||||
}
|
||||
|
||||
return make_ready_future<>();
|
||||
};
|
||||
|
||||
return when_all_succeed(modify_roles(), modify_role_members());
|
||||
}
|
||||
|
||||
future<>
|
||||
standard_role_manager::grant(stdx::string_view grantee_name, stdx::string_view role_name) const {
|
||||
const auto check_redundant = [this, role_name, grantee_name] {
|
||||
return this->query_granted(
|
||||
grantee_name,
|
||||
recursive_role_query::yes).then([role_name, grantee_name](role_set roles) {
|
||||
if (roles.count(sstring(role_name)) != 0) {
|
||||
throw role_already_included(grantee_name, role_name);
|
||||
}
|
||||
|
||||
return make_ready_future<>();
|
||||
});
|
||||
};
|
||||
|
||||
const auto check_cycle = [this, role_name, grantee_name] {
|
||||
return this->query_granted(
|
||||
role_name,
|
||||
recursive_role_query::yes).then([role_name, grantee_name](role_set roles) {
|
||||
if (roles.count(sstring(grantee_name)) != 0) {
|
||||
throw role_already_included(role_name, grantee_name);
|
||||
}
|
||||
|
||||
return make_ready_future<>();
|
||||
});
|
||||
};
|
||||
|
||||
return when_all_succeed(check_redundant(), check_cycle()).then([this, role_name, grantee_name] {
|
||||
return this->modify_membership(grantee_name, role_name, membership_change::add);
|
||||
});
|
||||
}
|
||||
|
||||
future<>
|
||||
standard_role_manager::revoke(stdx::string_view revokee_name, stdx::string_view role_name) const {
|
||||
return this->exists(role_name).then([this, revokee_name, role_name](bool role_exists) {
|
||||
if (!role_exists) {
|
||||
throw nonexistant_role(sstring(role_name));
|
||||
}
|
||||
}).then([this, revokee_name, role_name] {
|
||||
return this->query_granted(
|
||||
revokee_name,
|
||||
recursive_role_query::no).then([revokee_name, role_name](role_set roles) {
|
||||
if (roles.count(sstring(role_name)) == 0) {
|
||||
throw revoke_ungranted_role(revokee_name, role_name);
|
||||
}
|
||||
|
||||
return make_ready_future<>();
|
||||
}).then([this, revokee_name, role_name] {
|
||||
return this->modify_membership(revokee_name, role_name, membership_change::remove);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
static future<> collect_roles(
|
||||
cql3::query_processor& qp,
|
||||
stdx::string_view grantee_name,
|
||||
bool recurse,
|
||||
role_set& roles) {
|
||||
return require_record(qp, grantee_name).then([&qp, &roles, recurse](record r) {
|
||||
return do_with(std::move(r.member_of), [&qp, &roles, recurse](const role_set& memberships) {
|
||||
return do_for_each(memberships.begin(), memberships.end(), [&qp, &roles, recurse](const sstring& role_name) {
|
||||
roles.insert(role_name);
|
||||
|
||||
if (recurse) {
|
||||
return collect_roles(qp, role_name, true, roles);
|
||||
}
|
||||
|
||||
return make_ready_future<>();
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
future<role_set> standard_role_manager::query_granted(stdx::string_view grantee_name, recursive_role_query m) const {
|
||||
const bool recurse = (m == recursive_role_query::yes);
|
||||
|
||||
return do_with(
|
||||
role_set{sstring(grantee_name)},
|
||||
[this, grantee_name, recurse](role_set& roles) {
|
||||
return collect_roles(_qp, grantee_name, recurse, roles).then([&roles] { return roles; });
|
||||
});
|
||||
}
|
||||
|
||||
future<role_set> standard_role_manager::query_all() const {
|
||||
static const sstring query = sprint(
|
||||
"SELECT %s FROM %s",
|
||||
meta::roles_table::role_col_name,
|
||||
meta::roles_table::qualified_name());
|
||||
|
||||
// To avoid many copies of a view.
|
||||
static const auto role_col_name_string = sstring(meta::roles_table::role_col_name);
|
||||
|
||||
return _qp.process(query, db::consistency_level::QUORUM).then([](::shared_ptr<cql3::untyped_result_set> results) {
|
||||
role_set roles;
|
||||
|
||||
std::transform(
|
||||
results->begin(),
|
||||
results->end(),
|
||||
std::inserter(roles, roles.begin()),
|
||||
[](const cql3::untyped_result_set_row& row) {
|
||||
return row.get_as<sstring>(role_col_name_string);
|
||||
});
|
||||
|
||||
return roles;
|
||||
});
|
||||
}
|
||||
|
||||
future<bool> standard_role_manager::exists(stdx::string_view role_name) const {
|
||||
return find_record(_qp, role_name).then([](stdx::optional<record> mr) {
|
||||
return static_cast<bool>(mr);
|
||||
});
|
||||
}
|
||||
|
||||
future<bool> standard_role_manager::is_superuser(stdx::string_view role_name) const {
|
||||
return require_record(_qp, role_name).then([](record r) {
|
||||
return r.is_superuser;
|
||||
});
|
||||
}
|
||||
|
||||
future<bool> standard_role_manager::can_login(stdx::string_view role_name) const {
|
||||
return require_record(_qp, role_name).then([](record r) {
|
||||
return r.can_login;
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,105 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2017 ScyllaDB
|
||||
*/
|
||||
|
||||
/*
|
||||
* This file is part of Scylla.
|
||||
*
|
||||
* Scylla is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Scylla is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Scylla. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "auth/role_manager.hh"
|
||||
|
||||
#include <experimental/string_view>
|
||||
#include <unordered_set>
|
||||
|
||||
#include <seastar/core/abort_source.hh>
|
||||
#include <seastar/core/future.hh>
|
||||
#include <seastar/core/sstring.hh>
|
||||
|
||||
#include "stdx.hh"
|
||||
#include "seastarx.hh"
|
||||
|
||||
namespace cql3 {
|
||||
class query_processor;
|
||||
}
|
||||
|
||||
namespace service {
|
||||
class migration_manager;
|
||||
}
|
||||
|
||||
namespace auth {
|
||||
|
||||
stdx::string_view standard_role_manager_name() noexcept;
|
||||
|
||||
class standard_role_manager final : public role_manager {
|
||||
cql3::query_processor& _qp;
|
||||
::service::migration_manager& _migration_manager;
|
||||
future<> _stopped;
|
||||
seastar::abort_source _as;
|
||||
|
||||
public:
|
||||
standard_role_manager(cql3::query_processor& qp, ::service::migration_manager& mm)
|
||||
: _qp(qp)
|
||||
, _migration_manager(mm)
|
||||
, _stopped(make_ready_future<>()) {
|
||||
}
|
||||
|
||||
virtual stdx::string_view qualified_java_name() const noexcept override;
|
||||
|
||||
virtual const resource_set& protected_resources() const override;
|
||||
|
||||
virtual future<> start() override;
|
||||
|
||||
virtual future<> stop() override;
|
||||
|
||||
virtual future<> create(stdx::string_view role_name, const role_config&) const override;
|
||||
|
||||
virtual future<> drop(stdx::string_view role_name) const override;
|
||||
|
||||
virtual future<> alter(stdx::string_view role_name, const role_config_update&) const override;
|
||||
|
||||
virtual future<> grant(stdx::string_view grantee_name, stdx::string_view role_name) const override;
|
||||
|
||||
virtual future<> revoke(stdx::string_view revokee_name, stdx::string_view role_name) const override;
|
||||
|
||||
virtual future<role_set> query_granted(stdx::string_view grantee_name, recursive_role_query) const override;
|
||||
|
||||
virtual future<role_set> query_all() const override;
|
||||
|
||||
virtual future<bool> exists(stdx::string_view role_name) const override;
|
||||
|
||||
virtual future<bool> is_superuser(stdx::string_view role_name) const override;
|
||||
|
||||
virtual future<bool> can_login(stdx::string_view role_name) const override;
|
||||
|
||||
private:
|
||||
enum class membership_change { add, remove };
|
||||
|
||||
future<> create_metadata_tables_if_missing() const;
|
||||
|
||||
bool legacy_metadata_exists() const;
|
||||
|
||||
future<> migrate_legacy_metadata() const;
|
||||
|
||||
future<> create_default_role_if_missing() const;
|
||||
|
||||
future<> create_or_replace(stdx::string_view role_name, const role_config&) const;
|
||||
|
||||
future<> modify_membership(stdx::string_view role_name, stdx::string_view grantee_name, membership_change) const;
|
||||
};
|
||||
|
||||
}
|
||||
@@ -39,17 +39,20 @@
|
||||
* along with Scylla. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "auth/authenticated_user.hh"
|
||||
#include "auth/authenticator.hh"
|
||||
#include "auth/authorizer.hh"
|
||||
#include "auth/default_authorizer.hh"
|
||||
#include "auth/password_authenticator.hh"
|
||||
#include "auth/permission.hh"
|
||||
#include "authenticator.hh"
|
||||
#include "authenticated_user.hh"
|
||||
#include "authenticator.hh"
|
||||
#include "authorizer.hh"
|
||||
#include "password_authenticator.hh"
|
||||
#include "default_authorizer.hh"
|
||||
#include "permission.hh"
|
||||
#include "db/config.hh"
|
||||
#include "utils/class_registrator.hh"
|
||||
|
||||
namespace auth {
|
||||
|
||||
class service;
|
||||
|
||||
static const sstring PACKAGE_NAME("com.scylladb.auth.");
|
||||
|
||||
static const sstring& transitional_authenticator_name() {
|
||||
@@ -64,47 +67,38 @@ static const sstring& transitional_authorizer_name() {
|
||||
|
||||
class transitional_authenticator : public authenticator {
|
||||
std::unique_ptr<authenticator> _authenticator;
|
||||
|
||||
public:
|
||||
static const sstring PASSWORD_AUTHENTICATOR_NAME;
|
||||
|
||||
transitional_authenticator(cql3::query_processor& qp, ::service::migration_manager& mm)
|
||||
: transitional_authenticator(std::make_unique<password_authenticator>(qp, mm)) {
|
||||
}
|
||||
: transitional_authenticator(std::make_unique<password_authenticator>(qp, mm))
|
||||
{}
|
||||
transitional_authenticator(std::unique_ptr<authenticator> a)
|
||||
: _authenticator(std::move(a)) {
|
||||
}
|
||||
|
||||
virtual future<> start() override {
|
||||
: _authenticator(std::move(a))
|
||||
{}
|
||||
future<> start() override {
|
||||
return _authenticator->start();
|
||||
}
|
||||
|
||||
virtual future<> stop() override {
|
||||
future<> stop() override {
|
||||
return _authenticator->stop();
|
||||
}
|
||||
|
||||
virtual const sstring& qualified_java_name() const override {
|
||||
const sstring& qualified_java_name() const override {
|
||||
return transitional_authenticator_name();
|
||||
}
|
||||
|
||||
virtual bool require_authentication() const override {
|
||||
bool require_authentication() const override {
|
||||
return true;
|
||||
}
|
||||
|
||||
virtual authentication_option_set supported_options() const override {
|
||||
option_set supported_options() const override {
|
||||
return _authenticator->supported_options();
|
||||
}
|
||||
|
||||
virtual authentication_option_set alterable_options() const override {
|
||||
option_set alterable_options() const override {
|
||||
return _authenticator->alterable_options();
|
||||
}
|
||||
|
||||
virtual future<authenticated_user> authenticate(const credentials_map& credentials) const override {
|
||||
future<::shared_ptr<authenticated_user>> authenticate(const credentials_map& credentials) const override {
|
||||
auto i = credentials.find(authenticator::USERNAME_KEY);
|
||||
if ((i == credentials.end() || i->second.empty())
|
||||
&& (!credentials.count(PASSWORD_KEY) || credentials.at(PASSWORD_KEY).empty())) {
|
||||
if ((i == credentials.end() || i->second.empty()) && (!credentials.count(PASSWORD_KEY) || credentials.at(PASSWORD_KEY).empty())) {
|
||||
// return anon user
|
||||
return make_ready_future<authenticated_user>(anonymous_user());
|
||||
return make_ready_future<::shared_ptr<authenticated_user>>(::make_shared<authenticated_user>());
|
||||
}
|
||||
return make_ready_future().then([this, &credentials] {
|
||||
return _authenticator->authenticate(credentials);
|
||||
@@ -113,39 +107,29 @@ public:
|
||||
std::rethrow_exception(ep);
|
||||
} catch (exceptions::authentication_exception&) {
|
||||
// return anon user
|
||||
return make_ready_future<authenticated_user>(anonymous_user());
|
||||
return make_ready_future<::shared_ptr<authenticated_user>>(::make_shared<authenticated_user>());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
virtual future<> create(stdx::string_view role_name, const authentication_options& options) const override {
|
||||
return _authenticator->create(role_name, options);
|
||||
future<> create(sstring username, const option_map& options) override {
|
||||
return _authenticator->create(username, options);
|
||||
}
|
||||
|
||||
virtual future<> alter(stdx::string_view role_name, const authentication_options& options) const override {
|
||||
return _authenticator->alter(role_name, options);
|
||||
future<> alter(sstring username, const option_map& options) override {
|
||||
return _authenticator->alter(username, options);
|
||||
}
|
||||
|
||||
virtual future<> drop(stdx::string_view role_name) const override {
|
||||
return _authenticator->drop(role_name);
|
||||
future<> drop(sstring username) override {
|
||||
return _authenticator->drop(username);
|
||||
}
|
||||
|
||||
virtual future<custom_options> query_custom_options(stdx::string_view role_name) const override {
|
||||
return _authenticator->query_custom_options(role_name);
|
||||
}
|
||||
|
||||
virtual const resource_set& protected_resources() const override {
|
||||
const resource_ids& protected_resources() const override {
|
||||
return _authenticator->protected_resources();
|
||||
}
|
||||
|
||||
virtual ::shared_ptr<sasl_challenge> new_sasl_challenge() const override {
|
||||
::shared_ptr<sasl_challenge> new_sasl_challenge() const override {
|
||||
class sasl_wrapper : public sasl_challenge {
|
||||
public:
|
||||
sasl_wrapper(::shared_ptr<sasl_challenge> sasl)
|
||||
: _sasl(std::move(sasl)) {
|
||||
}
|
||||
|
||||
virtual bytes evaluate_response(bytes_view client_response) override {
|
||||
: _sasl(std::move(sasl))
|
||||
{}
|
||||
bytes evaluate_response(bytes_view client_response) override {
|
||||
try {
|
||||
return _sasl->evaluate_response(client_response);
|
||||
} catch (exceptions::authentication_exception&) {
|
||||
@@ -153,27 +137,23 @@ public:
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
virtual bool is_complete() const override {
|
||||
bool is_complete() const {
|
||||
return _complete || _sasl->is_complete();
|
||||
}
|
||||
|
||||
virtual future<authenticated_user> get_authenticated_user() const {
|
||||
future<::shared_ptr<authenticated_user>> get_authenticated_user() const {
|
||||
return futurize_apply([this] {
|
||||
return _sasl->get_authenticated_user().handle_exception([](auto ep) {
|
||||
try {
|
||||
std::rethrow_exception(ep);
|
||||
} catch (exceptions::authentication_exception&) {
|
||||
// return anon user
|
||||
return make_ready_future<authenticated_user>(anonymous_user());
|
||||
return make_ready_future<::shared_ptr<authenticated_user>>(::make_shared<authenticated_user>());
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private:
|
||||
::shared_ptr<sasl_challenge> _sasl;
|
||||
|
||||
bool _complete = false;
|
||||
};
|
||||
return ::make_shared<sasl_wrapper>(_authenticator->new_sasl_challenge());
|
||||
@@ -182,65 +162,55 @@ public:
|
||||
|
||||
class transitional_authorizer : public authorizer {
|
||||
std::unique_ptr<authorizer> _authorizer;
|
||||
|
||||
public:
|
||||
transitional_authorizer(cql3::query_processor& qp, ::service::migration_manager& mm)
|
||||
: transitional_authorizer(std::make_unique<default_authorizer>(qp, mm)) {
|
||||
}
|
||||
: transitional_authorizer(std::make_unique<default_authorizer>(qp, mm))
|
||||
{}
|
||||
transitional_authorizer(std::unique_ptr<authorizer> a)
|
||||
: _authorizer(std::move(a)) {
|
||||
}
|
||||
|
||||
~transitional_authorizer() {
|
||||
}
|
||||
|
||||
virtual future<> start() override {
|
||||
: _authorizer(std::move(a))
|
||||
{}
|
||||
~transitional_authorizer()
|
||||
{}
|
||||
future<> start() override {
|
||||
return _authorizer->start();
|
||||
}
|
||||
|
||||
virtual future<> stop() override {
|
||||
future<> stop() override {
|
||||
return _authorizer->stop();
|
||||
}
|
||||
|
||||
virtual const sstring& qualified_java_name() const override {
|
||||
const sstring& qualified_java_name() const override {
|
||||
return transitional_authorizer_name();
|
||||
}
|
||||
future<permission_set> authorize(service& ser, ::shared_ptr<authenticated_user> user, data_resource resource) const override {
|
||||
return is_super_user(ser, *user).then([](bool s) {
|
||||
static const permission_set transitional_permissions =
|
||||
permission_set::of<permission::CREATE,
|
||||
permission::ALTER, permission::DROP,
|
||||
permission::SELECT, permission::MODIFY>();
|
||||
|
||||
virtual future<permission_set> authorize(const role_or_anonymous&, const resource&) const override {
|
||||
static const permission_set transitional_permissions =
|
||||
permission_set::of<
|
||||
permission::CREATE,
|
||||
permission::ALTER,
|
||||
permission::DROP,
|
||||
permission::SELECT,
|
||||
permission::MODIFY>();
|
||||
|
||||
return make_ready_future<permission_set>(transitional_permissions);
|
||||
return make_ready_future<permission_set>(s ? permissions::ALL : transitional_permissions);
|
||||
});
|
||||
}
|
||||
|
||||
virtual future<> grant(stdx::string_view s, permission_set ps, const resource& r) const override {
|
||||
return _authorizer->grant(s, std::move(ps), r);
|
||||
future<> grant(::shared_ptr<authenticated_user> user, permission_set ps, data_resource r, sstring s) override {
|
||||
return _authorizer->grant(std::move(user), std::move(ps), std::move(r), std::move(s));
|
||||
}
|
||||
|
||||
virtual future<> revoke(stdx::string_view s, permission_set ps, const resource& r) const override {
|
||||
return _authorizer->revoke(s, std::move(ps), r);
|
||||
future<> revoke(::shared_ptr<authenticated_user> user, permission_set ps, data_resource r, sstring s) override {
|
||||
return _authorizer->revoke(std::move(user), std::move(ps), std::move(r), std::move(s));
|
||||
}
|
||||
|
||||
virtual future<std::vector<permission_details>> list_all() const override {
|
||||
return _authorizer->list_all();
|
||||
future<std::vector<permission_details>> list(service& ser, ::shared_ptr<authenticated_user> user, permission_set ps, optional<data_resource> r, optional<sstring> s) const override {
|
||||
return _authorizer->list(ser, std::move(user), std::move(ps), std::move(r), std::move(s));
|
||||
}
|
||||
|
||||
virtual future<> revoke_all(stdx::string_view s) const override {
|
||||
return _authorizer->revoke_all(s);
|
||||
future<> revoke_all(sstring s) override {
|
||||
return _authorizer->revoke_all(std::move(s));
|
||||
}
|
||||
|
||||
virtual future<> revoke_all(const resource& r) const override {
|
||||
return _authorizer->revoke_all(r);
|
||||
future<> revoke_all(data_resource r) override {
|
||||
return _authorizer->revoke_all(std::move(r));
|
||||
}
|
||||
|
||||
virtual const resource_set& protected_resources() const override {
|
||||
const resource_ids& protected_resources() override {
|
||||
return _authorizer->protected_resources();
|
||||
}
|
||||
future<> validate_configuration() const override {
|
||||
return _authorizer->validate_configuration();
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
@@ -253,10 +223,10 @@ static const class_registrator<
|
||||
auth::authenticator,
|
||||
auth::transitional_authenticator,
|
||||
cql3::query_processor&,
|
||||
::service::migration_manager&> transitional_authenticator_reg(auth::PACKAGE_NAME + "TransitionalAuthenticator");
|
||||
::service::migration_manager&> transitional_authenticator_reg("com.scylladb.auth.TransitionalAuthenticator");
|
||||
|
||||
static const class_registrator<
|
||||
auth::authorizer,
|
||||
auth::transitional_authorizer,
|
||||
cql3::query_processor&,
|
||||
::service::migration_manager&> transitional_authorizer_reg(auth::PACKAGE_NAME + "TransitionalAuthorizer");
|
||||
::service::migration_manager&> transitional_authorizer_reg("com.scylladb.auth.TransitionalAuthorizer");
|
||||
|
||||
@@ -1,138 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2017 ScyllaDB
|
||||
*/
|
||||
|
||||
/*
|
||||
* This file is part of Scylla.
|
||||
*
|
||||
* Scylla is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Scylla is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Scylla. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <seastar/core/scheduling.hh>
|
||||
#include <seastar/core/timer.hh>
|
||||
#include <seastar/core/gate.hh>
|
||||
#include <chrono>
|
||||
|
||||
// Simple proportional controller to adjust shares for processes for which a backlog can be clearly
|
||||
// defined.
|
||||
//
|
||||
// Goal is to consume the backlog as fast as we can, but not so fast that we steal all the CPU from
|
||||
// incoming requests, and at the same time minimize user-visible fluctuations in the quota.
|
||||
//
|
||||
// What that translates to is we'll try to keep the backlog's firt derivative at 0 (IOW, we keep
|
||||
// backlog constant). As the backlog grows we increase CPU usage, decreasing CPU usage as the
|
||||
// backlog diminishes.
|
||||
//
|
||||
// The exact point at which the controller stops determines the desired CPU usage. As the backlog
|
||||
// grows and approach a maximum desired, we need to be more aggressive. We will therefore define two
|
||||
// thresholds, and increase the constant as we cross them.
|
||||
//
|
||||
// Doing that divides the range in three (before the first, between first and second, and after
|
||||
// second threshold), and we'll be slow to grow in the first region, grow normally in the second
|
||||
// region, and aggressively in the third region.
|
||||
//
|
||||
// The constants q1 and q2 are used to determine the proportional factor at each stage.
|
||||
class backlog_controller {
|
||||
public:
|
||||
future<> shutdown() {
|
||||
_update_timer.cancel();
|
||||
return std::move(_inflight_update);
|
||||
}
|
||||
protected:
|
||||
struct control_point {
|
||||
float input;
|
||||
float output;
|
||||
};
|
||||
|
||||
seastar::scheduling_group _scheduling_group;
|
||||
const ::io_priority_class& _io_priority;
|
||||
std::chrono::milliseconds _interval;
|
||||
timer<> _update_timer;
|
||||
|
||||
std::vector<control_point> _control_points;
|
||||
|
||||
std::function<float()> _current_backlog;
|
||||
// updating shares for an I/O class may contact another shard and returns a future.
|
||||
future<> _inflight_update;
|
||||
|
||||
virtual void update_controller(float quota);
|
||||
|
||||
void adjust();
|
||||
|
||||
backlog_controller(seastar::scheduling_group sg, const ::io_priority_class& iop, std::chrono::milliseconds interval,
|
||||
std::vector<control_point> control_points, std::function<float()> backlog)
|
||||
: _scheduling_group(sg)
|
||||
, _io_priority(iop)
|
||||
, _interval(interval)
|
||||
, _update_timer([this] { adjust(); })
|
||||
, _control_points({{0,0}})
|
||||
, _current_backlog(std::move(backlog))
|
||||
, _inflight_update(make_ready_future<>())
|
||||
{
|
||||
_control_points.insert(_control_points.end(), control_points.begin(), control_points.end());
|
||||
_update_timer.arm_periodic(_interval);
|
||||
}
|
||||
|
||||
// Used when the controllers are disabled and a static share is used
|
||||
// When that option is deprecated we should remove this.
|
||||
backlog_controller(seastar::scheduling_group sg, const ::io_priority_class& iop, float static_shares)
|
||||
: _scheduling_group(sg)
|
||||
, _io_priority(iop)
|
||||
, _inflight_update(make_ready_future<>())
|
||||
{
|
||||
update_controller(static_shares);
|
||||
}
|
||||
|
||||
virtual ~backlog_controller() {}
|
||||
};
|
||||
|
||||
// memtable flush CPU controller.
|
||||
//
|
||||
// - First threshold is the soft limit line,
|
||||
// - Maximum is the point in which we'd stop consuming request,
|
||||
// - Second threshold is halfway between them.
|
||||
//
|
||||
// Below the soft limit, we are in no particular hurry to flush, since it means we're set to
|
||||
// complete flushing before we a new memtable is ready. The quota is dirty * q1, and q1 is set to a
|
||||
// low number.
|
||||
//
|
||||
// The first half of the virtual dirty region is where we expect to be usually, so we have a low
|
||||
// slope corresponding to a sluggish response between q1 * soft_limit and q2.
|
||||
//
|
||||
// In the second half, we're getting close to the hard dirty limit so we increase the slope and
|
||||
// become more responsive, up to a maximum quota of qmax.
|
||||
class flush_controller : public backlog_controller {
|
||||
static constexpr float hard_dirty_limit = 1.0f;
|
||||
public:
|
||||
flush_controller(seastar::scheduling_group sg, const ::io_priority_class& iop, float static_shares) : backlog_controller(sg, iop, static_shares) {}
|
||||
flush_controller(seastar::scheduling_group sg, const ::io_priority_class& iop, std::chrono::milliseconds interval, float soft_limit, std::function<float()> current_dirty)
|
||||
: backlog_controller(sg, iop, std::move(interval),
|
||||
std::vector<backlog_controller::control_point>({{soft_limit, 100}, {soft_limit + (hard_dirty_limit - soft_limit) / 2, 200} , {hard_dirty_limit, 1000}}),
|
||||
std::move(current_dirty)
|
||||
)
|
||||
{}
|
||||
};
|
||||
|
||||
class compaction_controller : public backlog_controller {
|
||||
public:
|
||||
static constexpr unsigned normalization_factor = 30;
|
||||
compaction_controller(seastar::scheduling_group sg, const ::io_priority_class& iop, float static_shares) : backlog_controller(sg, iop, static_shares) {}
|
||||
compaction_controller(seastar::scheduling_group sg, const ::io_priority_class& iop, std::chrono::milliseconds interval, std::function<float()> current_backlog)
|
||||
: backlog_controller(sg, iop, std::move(interval),
|
||||
std::vector<backlog_controller::control_point>({{0.5, 10}, {1.5, 100} , {normalization_factor, 1000}}),
|
||||
std::move(current_backlog)
|
||||
)
|
||||
{}
|
||||
};
|
||||
@@ -24,7 +24,7 @@
|
||||
#include <vector>
|
||||
#include "row_cache.hh"
|
||||
#include "mutation_reader.hh"
|
||||
#include "mutation_fragment.hh"
|
||||
#include "streamed_mutation.hh"
|
||||
#include "partition_version.hh"
|
||||
#include "utils/logalloc.hh"
|
||||
#include "query-request.hh"
|
||||
@@ -76,9 +76,19 @@ class cache_flat_mutation_reader final : public flat_mutation_reader::impl {
|
||||
|
||||
partition_snapshot_row_weakref _last_row;
|
||||
|
||||
// We need to be prepared that we may get overlapping and out of order
|
||||
// range tombstones. We must emit fragments with strictly monotonic positions,
|
||||
// so we can't just trim such tombstones to the position of the last fragment.
|
||||
// To solve that, range tombstones are accumulated first in a range_tombstone_stream
|
||||
// and emitted once we have a fragment with a larger position.
|
||||
range_tombstone_stream _tombstones;
|
||||
|
||||
// Holds the lower bound of a position range which hasn't been processed yet.
|
||||
// Only rows with positions < _lower_bound have been emitted, and only
|
||||
// range_tombstones with positions <= _lower_bound.
|
||||
// Only fragments with positions < _lower_bound have been emitted.
|
||||
//
|
||||
// It is assumed that !_lower_bound.is_clustering_row(). We depend on this when
|
||||
// calling range_tombstone::trim_front() and when inserting dummy entries. Dummy
|
||||
// entries are assumed to be only at !is_clustering_row() positions.
|
||||
position_in_partition _lower_bound;
|
||||
position_in_partition_view _upper_bound;
|
||||
|
||||
@@ -94,28 +104,26 @@ class cache_flat_mutation_reader final : public flat_mutation_reader::impl {
|
||||
// Valid when _state == reading_from_underlying.
|
||||
bool _population_range_starts_before_all_rows;
|
||||
|
||||
future<> do_fill_buffer(db::timeout_clock::time_point);
|
||||
future<> do_fill_buffer();
|
||||
void copy_from_cache_to_buffer();
|
||||
future<> process_static_row(db::timeout_clock::time_point);
|
||||
future<> process_static_row();
|
||||
void move_to_end();
|
||||
void move_to_next_range();
|
||||
void move_to_range(query::clustering_row_ranges::const_iterator);
|
||||
void move_to_next_entry();
|
||||
// Emits all delayed range tombstones with positions smaller than upper_bound.
|
||||
void drain_tombstones(position_in_partition_view upper_bound);
|
||||
// Emits all delayed range tombstones.
|
||||
void drain_tombstones();
|
||||
void add_to_buffer(const partition_snapshot_row_cursor&);
|
||||
void add_clustering_row_to_buffer(mutation_fragment&&);
|
||||
void add_to_buffer(range_tombstone&&);
|
||||
void add_to_buffer(mutation_fragment&&);
|
||||
future<> read_from_underlying(db::timeout_clock::time_point);
|
||||
future<> read_from_underlying();
|
||||
void start_reading_from_underlying();
|
||||
bool after_current_range(position_in_partition_view position);
|
||||
bool can_populate() const;
|
||||
// Marks the range between _last_row (exclusive) and _next_row (exclusive) as continuous,
|
||||
// provided that the underlying reader still matches the latest version of the partition.
|
||||
void maybe_update_continuity();
|
||||
// Tries to ensure that the lower bound of the current population range exists.
|
||||
// Returns false if it failed and range cannot be populated.
|
||||
// Assumes can_populate().
|
||||
bool ensure_population_lower_bound();
|
||||
void maybe_add_to_cache(const mutation_fragment& mf);
|
||||
void maybe_add_to_cache(const clustering_row& cr);
|
||||
void maybe_add_to_cache(const range_tombstone& rt);
|
||||
@@ -126,7 +134,6 @@ class cache_flat_mutation_reader final : public flat_mutation_reader::impl {
|
||||
_end_of_stream = true;
|
||||
_state = state::end_of_stream;
|
||||
}
|
||||
void touch_partition();
|
||||
public:
|
||||
cache_flat_mutation_reader(schema_ptr s,
|
||||
dht::decorated_key dk,
|
||||
@@ -141,6 +148,7 @@ public:
|
||||
, _ck_ranges_curr(_ck_ranges.begin())
|
||||
, _ck_ranges_end(_ck_ranges.end())
|
||||
, _lsa_manager(cache)
|
||||
, _tombstones(*_schema)
|
||||
, _lower_bound(position_in_partition::before_all_clustered_rows())
|
||||
, _upper_bound(position_in_partition_view::before_all_clustered_rows())
|
||||
, _read_context(std::move(ctx))
|
||||
@@ -151,7 +159,7 @@ public:
|
||||
}
|
||||
cache_flat_mutation_reader(const cache_flat_mutation_reader&) = delete;
|
||||
cache_flat_mutation_reader(cache_flat_mutation_reader&&) = delete;
|
||||
virtual future<> fill_buffer(db::timeout_clock::time_point timeout) override;
|
||||
virtual future<> fill_buffer() override;
|
||||
virtual ~cache_flat_mutation_reader() {
|
||||
maybe_merge_versions(_snp, _lsa_manager.region(), _lsa_manager.read_section());
|
||||
}
|
||||
@@ -161,30 +169,30 @@ public:
|
||||
_end_of_stream = true;
|
||||
}
|
||||
}
|
||||
virtual future<> fast_forward_to(const dht::partition_range&, db::timeout_clock::time_point timeout) override {
|
||||
virtual future<> fast_forward_to(const dht::partition_range&) override {
|
||||
clear_buffer();
|
||||
_end_of_stream = true;
|
||||
return make_ready_future<>();
|
||||
}
|
||||
virtual future<> fast_forward_to(position_range pr, db::timeout_clock::time_point timeout) override {
|
||||
virtual future<> fast_forward_to(position_range pr) override {
|
||||
throw std::bad_function_call();
|
||||
}
|
||||
};
|
||||
|
||||
inline
|
||||
future<> cache_flat_mutation_reader::process_static_row(db::timeout_clock::time_point timeout) {
|
||||
if (_snp->static_row_continuous()) {
|
||||
future<> cache_flat_mutation_reader::process_static_row() {
|
||||
if (_snp->version()->partition().static_row_continuous()) {
|
||||
_read_context->cache().on_row_hit();
|
||||
static_row sr = _lsa_manager.run_in_read_section([this] {
|
||||
return _snp->static_row(_read_context->digest_requested());
|
||||
row sr = _lsa_manager.run_in_read_section([this] {
|
||||
return _snp->static_row();
|
||||
});
|
||||
if (!sr.empty()) {
|
||||
push_mutation_fragment(mutation_fragment(std::move(sr)));
|
||||
push_mutation_fragment(mutation_fragment(static_row(std::move(sr))));
|
||||
}
|
||||
return make_ready_future<>();
|
||||
} else {
|
||||
_read_context->cache().on_row_miss();
|
||||
return _read_context->get_next_fragment(timeout).then([this] (mutation_fragment_opt&& sr) {
|
||||
return _read_context->get_next_fragment().then([this] (mutation_fragment_opt&& sr) {
|
||||
if (sr) {
|
||||
assert(sr->is_static_row());
|
||||
maybe_add_to_cache(sr->as_static_row());
|
||||
@@ -196,19 +204,10 @@ future<> cache_flat_mutation_reader::process_static_row(db::timeout_clock::time_
|
||||
}
|
||||
|
||||
inline
|
||||
void cache_flat_mutation_reader::touch_partition() {
|
||||
if (_snp->at_latest_version()) {
|
||||
rows_entry& last_dummy = *_snp->version()->partition().clustered_rows().rbegin();
|
||||
_snp->tracker()->touch(last_dummy);
|
||||
}
|
||||
}
|
||||
|
||||
inline
|
||||
future<> cache_flat_mutation_reader::fill_buffer(db::timeout_clock::time_point timeout) {
|
||||
future<> cache_flat_mutation_reader::fill_buffer() {
|
||||
if (_state == state::before_static_row) {
|
||||
auto after_static_row = [this, timeout] {
|
||||
auto after_static_row = [this] {
|
||||
if (_ck_ranges_curr == _ck_ranges_end) {
|
||||
touch_partition();
|
||||
finish_reader();
|
||||
return make_ready_future<>();
|
||||
}
|
||||
@@ -216,33 +215,33 @@ future<> cache_flat_mutation_reader::fill_buffer(db::timeout_clock::time_point t
|
||||
_lsa_manager.run_in_read_section([this] {
|
||||
move_to_range(_ck_ranges_curr);
|
||||
});
|
||||
return fill_buffer(timeout);
|
||||
return fill_buffer();
|
||||
};
|
||||
if (_schema->has_static_columns()) {
|
||||
return process_static_row(timeout).then(std::move(after_static_row));
|
||||
return process_static_row().then(std::move(after_static_row));
|
||||
} else {
|
||||
return after_static_row();
|
||||
}
|
||||
}
|
||||
clogger.trace("csm {}: fill_buffer(), range={}, lb={}", this, *_ck_ranges_curr, _lower_bound);
|
||||
return do_until([this] { return _end_of_stream || is_buffer_full(); }, [this, timeout] {
|
||||
return do_fill_buffer(timeout);
|
||||
return do_until([this] { return _end_of_stream || is_buffer_full(); }, [this] {
|
||||
return do_fill_buffer();
|
||||
});
|
||||
}
|
||||
|
||||
inline
|
||||
future<> cache_flat_mutation_reader::do_fill_buffer(db::timeout_clock::time_point timeout) {
|
||||
future<> cache_flat_mutation_reader::do_fill_buffer() {
|
||||
if (_state == state::move_to_underlying) {
|
||||
_state = state::reading_from_underlying;
|
||||
_population_range_starts_before_all_rows = _lower_bound.is_before_all_clustered_rows(*_schema);
|
||||
auto end = _next_row_in_range ? position_in_partition(_next_row.position())
|
||||
: position_in_partition(_upper_bound);
|
||||
return _read_context->fast_forward_to(position_range{_lower_bound, std::move(end)}, timeout).then([this, timeout] {
|
||||
return read_from_underlying(timeout);
|
||||
return _read_context->fast_forward_to(position_range{_lower_bound, std::move(end)}).then([this] {
|
||||
return read_from_underlying();
|
||||
});
|
||||
}
|
||||
if (_state == state::reading_from_underlying) {
|
||||
return read_from_underlying(timeout);
|
||||
return read_from_underlying();
|
||||
}
|
||||
// assert(_state == state::reading_from_cache)
|
||||
return _lsa_manager.run_in_read_section([this] {
|
||||
@@ -273,7 +272,7 @@ future<> cache_flat_mutation_reader::do_fill_buffer(db::timeout_clock::time_poin
|
||||
}
|
||||
|
||||
inline
|
||||
future<> cache_flat_mutation_reader::read_from_underlying(db::timeout_clock::time_point timeout) {
|
||||
future<> cache_flat_mutation_reader::read_from_underlying() {
|
||||
return consume_mutation_fragments_until(_read_context->underlying().underlying(),
|
||||
[this] { return _state != state::reading_from_underlying || is_buffer_full(); },
|
||||
[this] (mutation_fragment mf) {
|
||||
@@ -319,14 +318,13 @@ future<> cache_flat_mutation_reader::read_from_underlying(db::timeout_clock::tim
|
||||
auto inserted = insert_result.second;
|
||||
auto it = insert_result.first;
|
||||
if (inserted) {
|
||||
_snp->tracker()->insert(*e);
|
||||
e.release();
|
||||
auto next = std::next(it);
|
||||
it->set_continuous(next->continuous());
|
||||
clogger.trace("csm {}: inserted dummy at {}, cont={}", this, it->position(), it->continuous());
|
||||
}
|
||||
});
|
||||
} else if (ensure_population_lower_bound()) {
|
||||
} else if (!_ck_ranges_curr->start() || _last_row.refresh(*_snp)) {
|
||||
with_allocator(_snp->region().allocator(), [&] {
|
||||
auto e = alloc_strategy_unique_ptr<rows_entry>(
|
||||
current_allocator().construct<rows_entry>(*_schema, _upper_bound, is_dummy::yes, is_continuous::yes));
|
||||
@@ -335,7 +333,6 @@ future<> cache_flat_mutation_reader::read_from_underlying(db::timeout_clock::tim
|
||||
auto inserted = insert_result.second;
|
||||
if (inserted) {
|
||||
clogger.trace("csm {}: inserted dummy at {}", this, _upper_bound);
|
||||
_snp->tracker()->insert(*e);
|
||||
e.release();
|
||||
} else {
|
||||
clogger.trace("csm {}: mark {} as continuous", this, insert_result.first->position());
|
||||
@@ -358,43 +355,27 @@ future<> cache_flat_mutation_reader::read_from_underlying(db::timeout_clock::tim
|
||||
});
|
||||
}
|
||||
|
||||
inline
|
||||
bool cache_flat_mutation_reader::ensure_population_lower_bound() {
|
||||
if (_population_range_starts_before_all_rows) {
|
||||
return true;
|
||||
}
|
||||
if (!_last_row.refresh(*_snp)) {
|
||||
return false;
|
||||
}
|
||||
// Continuity flag we will later set for the upper bound extends to the previous row in the same version,
|
||||
// so we need to ensure we have an entry in the latest version.
|
||||
if (!_last_row.is_in_latest_version()) {
|
||||
with_allocator(_snp->region().allocator(), [&] {
|
||||
auto& rows = _snp->version()->partition().clustered_rows();
|
||||
rows_entry::compare less(*_schema);
|
||||
// FIXME: Avoid the copy by inserting an incomplete clustering row
|
||||
auto e = alloc_strategy_unique_ptr<rows_entry>(
|
||||
current_allocator().construct<rows_entry>(*_last_row));
|
||||
e->set_continuous(false);
|
||||
auto insert_result = rows.insert_check(rows.end(), *e, less);
|
||||
auto inserted = insert_result.second;
|
||||
if (inserted) {
|
||||
clogger.trace("csm {}: inserted lower bound dummy at {}", this, e->position());
|
||||
_snp->tracker()->insert(*e);
|
||||
e.release();
|
||||
}
|
||||
});
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
inline
|
||||
void cache_flat_mutation_reader::maybe_update_continuity() {
|
||||
if (can_populate() && ensure_population_lower_bound()) {
|
||||
with_allocator(_snp->region().allocator(), [&] {
|
||||
rows_entry& e = _next_row.ensure_entry_in_latest().row;
|
||||
e.set_continuous(true);
|
||||
});
|
||||
if (can_populate() && (_population_range_starts_before_all_rows || _last_row.refresh(*_snp))) {
|
||||
if (_next_row.is_in_latest_version()) {
|
||||
clogger.trace("csm {}: mark {} continuous", this, _next_row.get_iterator_in_latest_version()->position());
|
||||
_next_row.get_iterator_in_latest_version()->set_continuous(true);
|
||||
} else {
|
||||
// Cover entry from older version
|
||||
with_allocator(_snp->region().allocator(), [&] {
|
||||
auto& rows = _snp->version()->partition().clustered_rows();
|
||||
rows_entry::compare less(*_schema);
|
||||
auto e = alloc_strategy_unique_ptr<rows_entry>(
|
||||
current_allocator().construct<rows_entry>(*_schema, _next_row.position(), is_dummy(_next_row.dummy()), is_continuous::yes));
|
||||
auto insert_result = rows.insert_check(_next_row.get_iterator_in_latest_version(), *e, less);
|
||||
auto inserted = insert_result.second;
|
||||
if (inserted) {
|
||||
clogger.trace("csm {}: inserted dummy at {}", this, e->position());
|
||||
e.release();
|
||||
}
|
||||
});
|
||||
}
|
||||
} else {
|
||||
_read_context->cache().on_mispopulate();
|
||||
}
|
||||
@@ -424,9 +405,6 @@ void cache_flat_mutation_reader::maybe_add_to_cache(const clustering_row& cr) {
|
||||
mutation_partition& mp = _snp->version()->partition();
|
||||
rows_entry::compare less(*_schema);
|
||||
|
||||
if (_read_context->digest_requested()) {
|
||||
cr.cells().prepare_hash(*_schema, column_kind::regular_column);
|
||||
}
|
||||
auto new_entry = alloc_strategy_unique_ptr<rows_entry>(
|
||||
current_allocator().construct<rows_entry>(cr.key(), cr.tomb(), cr.marker(), cr.cells()));
|
||||
new_entry->set_continuous(false);
|
||||
@@ -434,20 +412,20 @@ void cache_flat_mutation_reader::maybe_add_to_cache(const clustering_row& cr) {
|
||||
: mp.clustered_rows().lower_bound(cr.key(), less);
|
||||
auto insert_result = mp.clustered_rows().insert_check(it, *new_entry, less);
|
||||
if (insert_result.second) {
|
||||
_snp->tracker()->insert(*new_entry);
|
||||
_read_context->cache().on_row_insert();
|
||||
new_entry.release();
|
||||
}
|
||||
it = insert_result.first;
|
||||
|
||||
rows_entry& e = *it;
|
||||
if (ensure_population_lower_bound()) {
|
||||
if (!_ck_ranges_curr->start() || _last_row.refresh(*_snp)) {
|
||||
clogger.trace("csm {}: set_continuous({})", this, e.position());
|
||||
e.set_continuous(true);
|
||||
} else {
|
||||
_read_context->cache().on_mispopulate();
|
||||
}
|
||||
with_allocator(standard_allocator(), [&] {
|
||||
_last_row = partition_snapshot_row_weakref(*_snp, it, true);
|
||||
_last_row = partition_snapshot_row_weakref(*_snp, it);
|
||||
});
|
||||
_population_range_starts_before_all_rows = false;
|
||||
});
|
||||
@@ -462,27 +440,18 @@ inline
|
||||
void cache_flat_mutation_reader::start_reading_from_underlying() {
|
||||
clogger.trace("csm {}: start_reading_from_underlying(), range=[{}, {})", this, _lower_bound, _next_row_in_range ? _next_row.position() : _upper_bound);
|
||||
_state = state::move_to_underlying;
|
||||
_next_row.touch();
|
||||
}
|
||||
|
||||
inline
|
||||
void cache_flat_mutation_reader::copy_from_cache_to_buffer() {
|
||||
clogger.trace("csm {}: copy_from_cache, next={}, next_row_in_range={}", this, _next_row.position(), _next_row_in_range);
|
||||
_next_row.touch();
|
||||
position_in_partition_view next_lower_bound = _next_row.dummy() ? _next_row.position() : position_in_partition_view::after_key(_next_row.key());
|
||||
for (auto &&rts : _snp->range_tombstones(_lower_bound, _next_row_in_range ? next_lower_bound : _upper_bound)) {
|
||||
// This guarantees that rts starts after any emitted clustering_row
|
||||
// and not before any emitted range tombstone.
|
||||
if (rts.trim_front(*_schema, _lower_bound)) {
|
||||
_lower_bound = position_in_partition(rts.position());
|
||||
if (is_buffer_full()) {
|
||||
return;
|
||||
}
|
||||
push_mutation_fragment(std::move(rts));
|
||||
for (auto&& rts : _snp->range_tombstones(*_schema, _lower_bound, _next_row_in_range ? next_lower_bound : _upper_bound)) {
|
||||
add_to_buffer(std::move(rts));
|
||||
if (is_buffer_full()) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
// We add the row to the buffer even when it's full.
|
||||
// This simplifies the code. For more info see #3139.
|
||||
if (_next_row_in_range) {
|
||||
_last_row = _next_row;
|
||||
add_to_buffer(_next_row);
|
||||
@@ -494,6 +463,7 @@ void cache_flat_mutation_reader::copy_from_cache_to_buffer() {
|
||||
|
||||
inline
|
||||
void cache_flat_mutation_reader::move_to_end() {
|
||||
drain_tombstones();
|
||||
finish_reader();
|
||||
clogger.trace("csm {}: eos", this);
|
||||
}
|
||||
@@ -535,8 +505,7 @@ void cache_flat_mutation_reader::move_to_range(query::clustering_row_ranges::con
|
||||
auto new_entry = current_allocator().construct<rows_entry>(*_schema, _lower_bound, is_dummy::yes, is_continuous::no);
|
||||
return rows.insert_before(_next_row.get_iterator_in_latest_version(), *new_entry);
|
||||
});
|
||||
_snp->tracker()->insert(*it);
|
||||
_last_row = partition_snapshot_row_weakref(*_snp, it, true);
|
||||
_last_row = partition_snapshot_row_weakref(*_snp, it);
|
||||
} else {
|
||||
_read_context->cache().on_mispopulate();
|
||||
}
|
||||
@@ -564,6 +533,30 @@ void cache_flat_mutation_reader::move_to_next_entry() {
|
||||
}
|
||||
}
|
||||
|
||||
inline
|
||||
void cache_flat_mutation_reader::drain_tombstones(position_in_partition_view pos) {
|
||||
while (true) {
|
||||
reserve_one();
|
||||
auto mfo = _tombstones.get_next(pos);
|
||||
if (!mfo) {
|
||||
break;
|
||||
}
|
||||
push_mutation_fragment(std::move(*mfo));
|
||||
}
|
||||
}
|
||||
|
||||
inline
|
||||
void cache_flat_mutation_reader::drain_tombstones() {
|
||||
while (true) {
|
||||
reserve_one();
|
||||
auto mfo = _tombstones.get_next();
|
||||
if (!mfo) {
|
||||
break;
|
||||
}
|
||||
push_mutation_fragment(std::move(*mfo));
|
||||
}
|
||||
}
|
||||
|
||||
inline
|
||||
void cache_flat_mutation_reader::add_to_buffer(mutation_fragment&& mf) {
|
||||
clogger.trace("csm {}: add_to_buffer({})", this, mf);
|
||||
@@ -579,7 +572,7 @@ inline
|
||||
void cache_flat_mutation_reader::add_to_buffer(const partition_snapshot_row_cursor& row) {
|
||||
if (!row.dummy()) {
|
||||
_read_context->cache().on_row_hit();
|
||||
add_clustering_row_to_buffer(row.row(_read_context->digest_requested()));
|
||||
add_clustering_row_to_buffer(row.row());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -590,21 +583,28 @@ inline
|
||||
void cache_flat_mutation_reader::add_clustering_row_to_buffer(mutation_fragment&& mf) {
|
||||
clogger.trace("csm {}: add_clustering_row_to_buffer({})", this, mf);
|
||||
auto& row = mf.as_clustering_row();
|
||||
auto new_lower_bound = position_in_partition::after_key(row.key());
|
||||
push_mutation_fragment(std::move(mf));
|
||||
_lower_bound = std::move(new_lower_bound);
|
||||
auto key = row.key();
|
||||
try {
|
||||
drain_tombstones(row.position());
|
||||
push_mutation_fragment(std::move(mf));
|
||||
_lower_bound = position_in_partition::after_key(std::move(key));
|
||||
} catch (...) {
|
||||
// We may have emitted some of the range tombstones which start after the old _lower_bound
|
||||
_lower_bound = position_in_partition::for_key(std::move(key));
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
inline
|
||||
void cache_flat_mutation_reader::add_to_buffer(range_tombstone&& rt) {
|
||||
clogger.trace("csm {}: add_to_buffer({})", this, rt);
|
||||
// This guarantees that rt starts after any emitted clustering_row
|
||||
// and not before any emitted range tombstone.
|
||||
if (!rt.trim_front(*_schema, _lower_bound)) {
|
||||
return;
|
||||
}
|
||||
_lower_bound = position_in_partition(rt.position());
|
||||
push_mutation_fragment(std::move(rt));
|
||||
_tombstones.apply(std::move(rt));
|
||||
drain_tombstones(_lower_bound);
|
||||
}
|
||||
|
||||
inline
|
||||
@@ -623,11 +623,8 @@ inline
|
||||
void cache_flat_mutation_reader::maybe_add_to_cache(const static_row& sr) {
|
||||
if (can_populate()) {
|
||||
clogger.trace("csm {}: populate({})", this, sr);
|
||||
_read_context->cache().on_static_row_insert();
|
||||
_read_context->cache().on_row_insert();
|
||||
_lsa_manager.run_in_update_section_with_allocator([&] {
|
||||
if (_read_context->digest_requested()) {
|
||||
sr.cells().prepare_hash(*_schema, column_kind::static_column);
|
||||
}
|
||||
_snp->version()->partition().static_row().apply(*_schema, column_kind::static_column, sr.cells());
|
||||
});
|
||||
} else {
|
||||
|
||||
@@ -75,7 +75,7 @@ mutation canonical_mutation::to_mutation(schema_ptr s) const {
|
||||
auto version = mv.schema_version();
|
||||
auto pk = mv.key();
|
||||
|
||||
mutation m(std::move(s), std::move(pk));
|
||||
mutation m(std::move(pk), std::move(s));
|
||||
|
||||
if (version == m.schema()->version()) {
|
||||
auto partition_view = mutation_partition_view::from_view(mv.partition());
|
||||
|
||||
@@ -39,11 +39,9 @@ using small_vector = std::vector<T>;
|
||||
#endif
|
||||
|
||||
#include "fnv1a_hasher.hh"
|
||||
#include "mutation_fragment.hh"
|
||||
#include "streamed_mutation.hh"
|
||||
#include "mutation_partition.hh"
|
||||
|
||||
#include "db/timeout_clock.hh"
|
||||
|
||||
class cells_range {
|
||||
using ids_vector_type = small_vector<column_id, 5>;
|
||||
|
||||
@@ -144,7 +142,11 @@ struct cell_locker_stats {
|
||||
};
|
||||
|
||||
class cell_locker {
|
||||
public:
|
||||
using timeout_clock = lowres_clock;
|
||||
private:
|
||||
using semaphore_type = basic_semaphore<default_timeout_exception_factory, timeout_clock>;
|
||||
|
||||
class partition_entry;
|
||||
|
||||
struct cell_address {
|
||||
@@ -156,7 +158,7 @@ private:
|
||||
public enable_lw_shared_from_this<cell_entry> {
|
||||
partition_entry& _parent;
|
||||
cell_address _address;
|
||||
db::timeout_semaphore _semaphore { 0 };
|
||||
semaphore_type _semaphore { 0 };
|
||||
|
||||
friend class cell_locker;
|
||||
public:
|
||||
@@ -185,7 +187,7 @@ private:
|
||||
return _address.position;
|
||||
}
|
||||
|
||||
future<> lock(db::timeout_clock::time_point _timeout) {
|
||||
future<> lock(timeout_clock::time_point _timeout) {
|
||||
return _semaphore.wait(_timeout);
|
||||
}
|
||||
void unlock() {
|
||||
@@ -385,7 +387,7 @@ public:
|
||||
|
||||
// partition_cells_range is required to be in cell_locker::schema()
|
||||
future<std::vector<locked_cell>> lock_cells(const dht::decorated_key& dk, partition_cells_range&& range,
|
||||
db::timeout_clock::time_point timeout);
|
||||
timeout_clock::time_point timeout);
|
||||
};
|
||||
|
||||
|
||||
@@ -414,7 +416,7 @@ struct cell_locker::locker {
|
||||
partition_cells_range::iterator _current_ck;
|
||||
cells_range::const_iterator _current_cell;
|
||||
|
||||
db::timeout_clock::time_point _timeout;
|
||||
timeout_clock::time_point _timeout;
|
||||
std::vector<locked_cell> _locks;
|
||||
cell_locker_stats& _stats;
|
||||
private:
|
||||
@@ -428,7 +430,7 @@ private:
|
||||
|
||||
bool is_done() const { return _current_ck == _range.end(); }
|
||||
public:
|
||||
explicit locker(const ::schema& s, cell_locker_stats& st, partition_entry& pe, partition_cells_range&& range, db::timeout_clock::time_point timeout)
|
||||
explicit locker(const ::schema& s, cell_locker_stats& st, partition_entry& pe, partition_cells_range&& range, timeout_clock::time_point timeout)
|
||||
: _hasher(s)
|
||||
, _eq_cmp(s)
|
||||
, _partition_entry(pe)
|
||||
@@ -456,7 +458,7 @@ public:
|
||||
};
|
||||
|
||||
inline
|
||||
future<std::vector<locked_cell>> cell_locker::lock_cells(const dht::decorated_key& dk, partition_cells_range&& range, db::timeout_clock::time_point timeout) {
|
||||
future<std::vector<locked_cell>> cell_locker::lock_cells(const dht::decorated_key& dk, partition_cells_range&& range, timeout_clock::time_point timeout) {
|
||||
partition_entry::hasher pe_hash;
|
||||
partition_entry::equal_compare pe_eq(*_schema);
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
|
||||
#include "schema.hh"
|
||||
#include "query-request.hh"
|
||||
#include "mutation_fragment.hh"
|
||||
#include "streamed_mutation.hh"
|
||||
|
||||
// Utility for in-order checking of overlap with position ranges.
|
||||
class clustering_ranges_walker {
|
||||
|
||||
@@ -23,7 +23,6 @@
|
||||
|
||||
#include "sstables/shared_sstable.hh"
|
||||
#include "exceptions/exceptions.hh"
|
||||
#include "sstables/compaction_backlog_manager.hh"
|
||||
|
||||
class column_family;
|
||||
class schema;
|
||||
@@ -121,8 +120,6 @@ public:
|
||||
}
|
||||
|
||||
sstable_set make_sstable_set(schema_ptr schema) const;
|
||||
|
||||
compaction_backlog_tracker& get_backlog_tracker();
|
||||
};
|
||||
|
||||
// Creates a compaction_strategy object from one of the strategies available.
|
||||
|
||||
@@ -28,7 +28,6 @@
|
||||
#include <boost/range/iterator_range.hpp>
|
||||
#include <boost/range/adaptor/transformed.hpp>
|
||||
#include "utils/serialization.hh"
|
||||
#include "util/backtrace.hh"
|
||||
#include "unimplemented.hh"
|
||||
|
||||
enum class allow_prefixes { no, yes };
|
||||
@@ -145,7 +144,7 @@ public:
|
||||
}
|
||||
len = read_simple<size_type>(_v);
|
||||
if (_v.size() < len) {
|
||||
throw_with_backtrace<marshal_exception>(sprint("compound_type iterator - not enough bytes, expected %d, got %d", len, _v.size()));
|
||||
throw marshal_exception();
|
||||
}
|
||||
}
|
||||
_current = bytes_view(_v.begin(), len);
|
||||
|
||||
@@ -345,7 +345,7 @@ public:
|
||||
}
|
||||
len = read_simple<size_type>(_v);
|
||||
if (_v.size() < len) {
|
||||
throw_with_backtrace<marshal_exception>(sprint("composite iterator - not enough bytes, expected %d, got %d", len, _v.size()));
|
||||
throw marshal_exception();
|
||||
}
|
||||
}
|
||||
auto value = bytes_view(_v.begin(), len);
|
||||
|
||||
345
compress.cc
345
compress.cc
@@ -1,345 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2016 ScyllaDB
|
||||
*/
|
||||
|
||||
/*
|
||||
* This file is part of Scylla.
|
||||
*
|
||||
* Scylla is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Scylla is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Scylla. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <lz4.h>
|
||||
#include <zlib.h>
|
||||
#include <snappy-c.h>
|
||||
|
||||
#include "compress.hh"
|
||||
#include "utils/class_registrator.hh"
|
||||
|
||||
const sstring compressor::namespace_prefix = "org.apache.cassandra.io.compress.";
|
||||
|
||||
class lz4_processor: public compressor {
|
||||
public:
|
||||
using compressor::compressor;
|
||||
|
||||
size_t uncompress(const char* input, size_t input_len, char* output,
|
||||
size_t output_len) const override;
|
||||
size_t compress(const char* input, size_t input_len, char* output,
|
||||
size_t output_len) const override;
|
||||
size_t compress_max_size(size_t input_len) const override;
|
||||
};
|
||||
|
||||
class snappy_processor: public compressor {
|
||||
public:
|
||||
using compressor::compressor;
|
||||
|
||||
size_t uncompress(const char* input, size_t input_len, char* output,
|
||||
size_t output_len) const override;
|
||||
size_t compress(const char* input, size_t input_len, char* output,
|
||||
size_t output_len) const override;
|
||||
size_t compress_max_size(size_t input_len) const override;
|
||||
};
|
||||
|
||||
class deflate_processor: public compressor {
|
||||
public:
|
||||
using compressor::compressor;
|
||||
|
||||
size_t uncompress(const char* input, size_t input_len, char* output,
|
||||
size_t output_len) const override;
|
||||
size_t compress(const char* input, size_t input_len, char* output,
|
||||
size_t output_len) const override;
|
||||
size_t compress_max_size(size_t input_len) const override;
|
||||
};
|
||||
|
||||
compressor::compressor(sstring name)
|
||||
: _name(std::move(name))
|
||||
{}
|
||||
|
||||
std::set<sstring> compressor::option_names() const {
|
||||
return {};
|
||||
}
|
||||
|
||||
std::map<sstring, sstring> compressor::options() const {
|
||||
return {};
|
||||
}
|
||||
|
||||
shared_ptr<compressor> compressor::create(const sstring& name, const opt_getter& opts) {
|
||||
if (name.empty()) {
|
||||
return {};
|
||||
}
|
||||
|
||||
qualified_name qn(namespace_prefix, name);
|
||||
|
||||
for (auto& c : { lz4, snappy, deflate }) {
|
||||
if (c->name() == qn) {
|
||||
return c;
|
||||
}
|
||||
}
|
||||
|
||||
return compressor_registry::create(qn, opts);
|
||||
}
|
||||
|
||||
shared_ptr<compressor> compressor::create(const std::map<sstring, sstring>& options) {
|
||||
auto i = options.find(compression_parameters::SSTABLE_COMPRESSION);
|
||||
if (i != options.end() && !i->second.empty()) {
|
||||
return create(i->second, [&options](const sstring& key) -> opt_string {
|
||||
auto i = options.find(key);
|
||||
if (i == options.end()) {
|
||||
return std::experimental::nullopt;
|
||||
}
|
||||
return { i->second };
|
||||
});
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
thread_local const shared_ptr<compressor> compressor::lz4 = make_shared<lz4_processor>(namespace_prefix + "LZ4Compressor");
|
||||
thread_local const shared_ptr<compressor> compressor::snappy = make_shared<snappy_processor>(namespace_prefix + "SnappyCompressor");
|
||||
thread_local const shared_ptr<compressor> compressor::deflate = make_shared<deflate_processor>(namespace_prefix + "DeflateCompressor");
|
||||
|
||||
const sstring compression_parameters::SSTABLE_COMPRESSION = "sstable_compression";
|
||||
const sstring compression_parameters::CHUNK_LENGTH_KB = "chunk_length_kb";
|
||||
const sstring compression_parameters::CRC_CHECK_CHANCE = "crc_check_chance";
|
||||
|
||||
compression_parameters::compression_parameters()
|
||||
: compression_parameters(nullptr)
|
||||
{}
|
||||
|
||||
compression_parameters::~compression_parameters()
|
||||
{}
|
||||
|
||||
compression_parameters::compression_parameters(compressor_ptr c)
|
||||
: _compressor(std::move(c))
|
||||
{}
|
||||
|
||||
compression_parameters::compression_parameters(const std::map<sstring, sstring>& options) {
|
||||
_compressor = compressor::create(options);
|
||||
|
||||
validate_options(options);
|
||||
|
||||
auto chunk_length = options.find(CHUNK_LENGTH_KB);
|
||||
if (chunk_length != options.end()) {
|
||||
try {
|
||||
_chunk_length = std::stoi(chunk_length->second) * 1024;
|
||||
} catch (const std::exception& e) {
|
||||
throw exceptions::syntax_exception(sstring("Invalid integer value ") + chunk_length->second + " for " + CHUNK_LENGTH_KB);
|
||||
}
|
||||
}
|
||||
auto crc_chance = options.find(CRC_CHECK_CHANCE);
|
||||
if (crc_chance != options.end()) {
|
||||
try {
|
||||
_crc_check_chance = std::stod(crc_chance->second);
|
||||
} catch (const std::exception& e) {
|
||||
throw exceptions::syntax_exception(sstring("Invalid double value ") + crc_chance->second + "for " + CRC_CHECK_CHANCE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void compression_parameters::validate() {
|
||||
if (_chunk_length) {
|
||||
auto chunk_length = _chunk_length.value();
|
||||
if (chunk_length <= 0) {
|
||||
throw exceptions::configuration_exception(sstring("Invalid negative or null ") + CHUNK_LENGTH_KB);
|
||||
}
|
||||
// _chunk_length must be a power of two
|
||||
if (chunk_length & (chunk_length - 1)) {
|
||||
throw exceptions::configuration_exception(sstring(CHUNK_LENGTH_KB) + " must be a power of 2.");
|
||||
}
|
||||
}
|
||||
if (_crc_check_chance && (_crc_check_chance.value() < 0.0 || _crc_check_chance.value() > 1.0)) {
|
||||
throw exceptions::configuration_exception(sstring(CRC_CHECK_CHANCE) + " must be between 0.0 and 1.0.");
|
||||
}
|
||||
}
|
||||
|
||||
std::map<sstring, sstring> compression_parameters::get_options() const {
|
||||
if (!_compressor) {
|
||||
return std::map<sstring, sstring>();
|
||||
}
|
||||
auto opts = _compressor->options();
|
||||
|
||||
opts.emplace(compression_parameters::SSTABLE_COMPRESSION, _compressor->name());
|
||||
if (_chunk_length) {
|
||||
opts.emplace(sstring(CHUNK_LENGTH_KB), std::to_string(_chunk_length.value() / 1024));
|
||||
}
|
||||
if (_crc_check_chance) {
|
||||
opts.emplace(sstring(CRC_CHECK_CHANCE), std::to_string(_crc_check_chance.value()));
|
||||
}
|
||||
return opts;
|
||||
}
|
||||
|
||||
bool compression_parameters::operator==(const compression_parameters& other) const {
|
||||
return _compressor == other._compressor
|
||||
&& _chunk_length == other._chunk_length
|
||||
&& _crc_check_chance == other._crc_check_chance;
|
||||
}
|
||||
|
||||
bool compression_parameters::operator!=(const compression_parameters& other) const {
|
||||
return !(*this == other);
|
||||
}
|
||||
|
||||
void compression_parameters::validate_options(const std::map<sstring, sstring>& options) {
|
||||
// currently, there are no options specific to a particular compressor
|
||||
static std::set<sstring> keywords({
|
||||
sstring(SSTABLE_COMPRESSION),
|
||||
sstring(CHUNK_LENGTH_KB),
|
||||
sstring(CRC_CHECK_CHANCE),
|
||||
});
|
||||
std::set<sstring> ckw;
|
||||
if (_compressor) {
|
||||
ckw = _compressor->option_names();
|
||||
}
|
||||
for (auto&& opt : options) {
|
||||
if (!keywords.count(opt.first) && !ckw.count(opt.first)) {
|
||||
throw exceptions::configuration_exception(sprint("Unknown compression option '%s'.", opt.first));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
size_t lz4_processor::uncompress(const char* input, size_t input_len,
|
||||
char* output, size_t output_len) const {
|
||||
// We use LZ4_decompress_safe(). According to the documentation, the
|
||||
// function LZ4_decompress_fast() is slightly faster, but maliciously
|
||||
// crafted compressed data can cause it to overflow the output buffer.
|
||||
// Theoretically, our compressed data is created by us so is not malicious
|
||||
// (and accidental corruption is avoided by the compressed-data checksum),
|
||||
// but let's not take that chance for now, until we've actually measured
|
||||
// the performance benefit that LZ4_decompress_fast() would bring.
|
||||
|
||||
// Cassandra's LZ4Compressor prepends to the chunk its uncompressed length
|
||||
// in 4 bytes little-endian (!) order. We don't need this information -
|
||||
// we already know the uncompressed data is at most the given chunk size
|
||||
// (and usually is exactly that, except in the last chunk). The advance
|
||||
// knowledge of the uncompressed size could be useful if we used
|
||||
// LZ4_decompress_fast(), but we prefer LZ4_decompress_safe() anyway...
|
||||
input += 4;
|
||||
input_len -= 4;
|
||||
|
||||
auto ret = LZ4_decompress_safe(input, output, input_len, output_len);
|
||||
if (ret < 0) {
|
||||
throw std::runtime_error("LZ4 uncompression failure");
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
size_t lz4_processor::compress(const char* input, size_t input_len,
|
||||
char* output, size_t output_len) const {
|
||||
if (output_len < LZ4_COMPRESSBOUND(input_len) + 4) {
|
||||
throw std::runtime_error("LZ4 compression failure: length of output is too small");
|
||||
}
|
||||
// Write input_len (32-bit data) to beginning of output in little-endian representation.
|
||||
output[0] = input_len & 0xFF;
|
||||
output[1] = (input_len >> 8) & 0xFF;
|
||||
output[2] = (input_len >> 16) & 0xFF;
|
||||
output[3] = (input_len >> 24) & 0xFF;
|
||||
#ifdef HAVE_LZ4_COMPRESS_DEFAULT
|
||||
auto ret = LZ4_compress_default(input, output + 4, input_len, LZ4_compressBound(input_len));
|
||||
#else
|
||||
auto ret = LZ4_compress(input, output + 4, input_len);
|
||||
#endif
|
||||
if (ret == 0) {
|
||||
throw std::runtime_error("LZ4 compression failure: LZ4_compress() failed");
|
||||
}
|
||||
return ret + 4;
|
||||
}
|
||||
|
||||
size_t lz4_processor::compress_max_size(size_t input_len) const {
|
||||
return LZ4_COMPRESSBOUND(input_len) + 4;
|
||||
}
|
||||
|
||||
size_t deflate_processor::uncompress(const char* input,
|
||||
size_t input_len, char* output, size_t output_len) const {
|
||||
z_stream zs;
|
||||
zs.zalloc = Z_NULL;
|
||||
zs.zfree = Z_NULL;
|
||||
zs.opaque = Z_NULL;
|
||||
zs.avail_in = 0;
|
||||
zs.next_in = Z_NULL;
|
||||
if (inflateInit(&zs) != Z_OK) {
|
||||
throw std::runtime_error("deflate uncompression init failure");
|
||||
}
|
||||
// yuck, zlib is not const-correct, and also uses unsigned char while we use char :-(
|
||||
zs.next_in = reinterpret_cast<unsigned char*>(const_cast<char*>(input));
|
||||
zs.avail_in = input_len;
|
||||
zs.next_out = reinterpret_cast<unsigned char*>(output);
|
||||
zs.avail_out = output_len;
|
||||
auto res = inflate(&zs, Z_FINISH);
|
||||
inflateEnd(&zs);
|
||||
if (res == Z_STREAM_END) {
|
||||
return output_len - zs.avail_out;
|
||||
} else {
|
||||
throw std::runtime_error("deflate uncompression failure");
|
||||
}
|
||||
}
|
||||
|
||||
size_t deflate_processor::compress(const char* input,
|
||||
size_t input_len, char* output, size_t output_len) const {
|
||||
z_stream zs;
|
||||
zs.zalloc = Z_NULL;
|
||||
zs.zfree = Z_NULL;
|
||||
zs.opaque = Z_NULL;
|
||||
zs.avail_in = 0;
|
||||
zs.next_in = Z_NULL;
|
||||
if (deflateInit(&zs, Z_DEFAULT_COMPRESSION) != Z_OK) {
|
||||
throw std::runtime_error("deflate compression init failure");
|
||||
}
|
||||
zs.next_in = reinterpret_cast<unsigned char*>(const_cast<char*>(input));
|
||||
zs.avail_in = input_len;
|
||||
zs.next_out = reinterpret_cast<unsigned char*>(output);
|
||||
zs.avail_out = output_len;
|
||||
auto res = ::deflate(&zs, Z_FINISH);
|
||||
deflateEnd(&zs);
|
||||
if (res == Z_STREAM_END) {
|
||||
return output_len - zs.avail_out;
|
||||
} else {
|
||||
throw std::runtime_error("deflate compression failure");
|
||||
}
|
||||
}
|
||||
|
||||
size_t deflate_processor::compress_max_size(size_t input_len) const {
|
||||
z_stream zs;
|
||||
zs.zalloc = Z_NULL;
|
||||
zs.zfree = Z_NULL;
|
||||
zs.opaque = Z_NULL;
|
||||
zs.avail_in = 0;
|
||||
zs.next_in = Z_NULL;
|
||||
if (deflateInit(&zs, Z_DEFAULT_COMPRESSION) != Z_OK) {
|
||||
throw std::runtime_error("deflate compression init failure");
|
||||
}
|
||||
auto res = deflateBound(&zs, input_len);
|
||||
deflateEnd(&zs);
|
||||
return res;
|
||||
}
|
||||
|
||||
size_t snappy_processor::uncompress(const char* input, size_t input_len,
|
||||
char* output, size_t output_len) const {
|
||||
if (snappy_uncompress(input, input_len, output, &output_len)
|
||||
== SNAPPY_OK) {
|
||||
return output_len;
|
||||
} else {
|
||||
throw std::runtime_error("snappy uncompression failure");
|
||||
}
|
||||
}
|
||||
|
||||
size_t snappy_processor::compress(const char* input, size_t input_len,
|
||||
char* output, size_t output_len) const {
|
||||
auto ret = snappy_compress(input, input_len, output, &output_len);
|
||||
if (ret != SNAPPY_OK) {
|
||||
throw std::runtime_error("snappy compression failure: snappy_compress() failed");
|
||||
}
|
||||
return output_len;
|
||||
}
|
||||
|
||||
size_t snappy_processor::compress_max_size(size_t input_len) const {
|
||||
return snappy_max_compressed_length(input_len);
|
||||
}
|
||||
|
||||
194
compress.hh
194
compress.hh
@@ -21,103 +21,135 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <map>
|
||||
#include <set>
|
||||
|
||||
#include <seastar/core/future.hh>
|
||||
#include <seastar/core/shared_ptr.hh>
|
||||
#include <seastar/core/sstring.hh>
|
||||
|
||||
#include "exceptions/exceptions.hh"
|
||||
#include "stdx.hh"
|
||||
|
||||
|
||||
class compressor {
|
||||
sstring _name;
|
||||
public:
|
||||
compressor(sstring);
|
||||
|
||||
virtual ~compressor() {}
|
||||
|
||||
/**
|
||||
* Unpacks data in "input" to output. If output_len is of insufficient size,
|
||||
* exception is thrown. I.e. you should keep track of the uncompressed size.
|
||||
*/
|
||||
virtual size_t uncompress(const char* input, size_t input_len, char* output,
|
||||
size_t output_len) const = 0;
|
||||
/**
|
||||
* Packs data in "input" to output. If output_len is of insufficient size,
|
||||
* exception is thrown. Maximum required size is obtained via "compress_max_size"
|
||||
*/
|
||||
virtual size_t compress(const char* input, size_t input_len, char* output,
|
||||
size_t output_len) const = 0;
|
||||
/**
|
||||
* Returns the maximum output size for compressing data on "input_len" size.
|
||||
*/
|
||||
virtual size_t compress_max_size(size_t input_len) const = 0;
|
||||
|
||||
/**
|
||||
* Returns accepted option names for this compressor
|
||||
*/
|
||||
virtual std::set<sstring> option_names() const;
|
||||
/**
|
||||
* Returns original options used in instantiating this compressor
|
||||
*/
|
||||
virtual std::map<sstring, sstring> options() const;
|
||||
|
||||
/**
|
||||
* Compressor class name.
|
||||
*/
|
||||
const sstring& name() const {
|
||||
return _name;
|
||||
}
|
||||
|
||||
// to cheaply bridge sstable compression options / maps
|
||||
using opt_string = stdx::optional<sstring>;
|
||||
using opt_getter = std::function<opt_string(const sstring&)>;
|
||||
|
||||
static shared_ptr<compressor> create(const sstring& name, const opt_getter&);
|
||||
static shared_ptr<compressor> create(const std::map<sstring, sstring>&);
|
||||
|
||||
static thread_local const shared_ptr<compressor> lz4;
|
||||
static thread_local const shared_ptr<compressor> snappy;
|
||||
static thread_local const shared_ptr<compressor> deflate;
|
||||
|
||||
static const sstring namespace_prefix;
|
||||
enum class compressor {
|
||||
none,
|
||||
lz4,
|
||||
snappy,
|
||||
deflate,
|
||||
};
|
||||
|
||||
template<typename BaseType, typename... Args>
|
||||
class class_registry;
|
||||
|
||||
using compressor_ptr = shared_ptr<compressor>;
|
||||
using compressor_registry = class_registry<compressor_ptr, const typename compressor::opt_getter&>;
|
||||
|
||||
class compression_parameters {
|
||||
public:
|
||||
static constexpr int32_t DEFAULT_CHUNK_LENGTH = 4 * 1024;
|
||||
static constexpr double DEFAULT_CRC_CHECK_CHANCE = 1.0;
|
||||
|
||||
static const sstring SSTABLE_COMPRESSION;
|
||||
static const sstring CHUNK_LENGTH_KB;
|
||||
static const sstring CRC_CHECK_CHANCE;
|
||||
static constexpr auto SSTABLE_COMPRESSION = "sstable_compression";
|
||||
static constexpr auto CHUNK_LENGTH_KB = "chunk_length_kb";
|
||||
static constexpr auto CRC_CHECK_CHANCE = "crc_check_chance";
|
||||
private:
|
||||
compressor_ptr _compressor;
|
||||
compressor _compressor;
|
||||
std::experimental::optional<int> _chunk_length;
|
||||
std::experimental::optional<double> _crc_check_chance;
|
||||
public:
|
||||
compression_parameters();
|
||||
compression_parameters(compressor_ptr);
|
||||
compression_parameters(const std::map<sstring, sstring>& options);
|
||||
~compression_parameters();
|
||||
compression_parameters(compressor c = compressor::lz4) : _compressor(c) { }
|
||||
compression_parameters(const std::map<sstring, sstring>& options) {
|
||||
validate_options(options);
|
||||
|
||||
compressor_ptr get_compressor() const { return _compressor; }
|
||||
auto it = options.find(SSTABLE_COMPRESSION);
|
||||
if (it == options.end() || it->second.empty()) {
|
||||
_compressor = compressor::none;
|
||||
return;
|
||||
}
|
||||
const auto& compressor_class = it->second;
|
||||
if (is_compressor_class(compressor_class, "LZ4Compressor")) {
|
||||
_compressor = compressor::lz4;
|
||||
} else if (is_compressor_class(compressor_class, "SnappyCompressor")) {
|
||||
_compressor = compressor::snappy;
|
||||
} else if (is_compressor_class(compressor_class, "DeflateCompressor")) {
|
||||
_compressor = compressor::deflate;
|
||||
} else {
|
||||
throw exceptions::configuration_exception(sstring("Unsupported compression class '") + compressor_class + "'.");
|
||||
}
|
||||
auto chunk_length = options.find(CHUNK_LENGTH_KB);
|
||||
if (chunk_length != options.end()) {
|
||||
try {
|
||||
_chunk_length = std::stoi(chunk_length->second) * 1024;
|
||||
} catch (const std::exception& e) {
|
||||
throw exceptions::syntax_exception(sstring("Invalid integer value ") + chunk_length->second + " for " + CHUNK_LENGTH_KB);
|
||||
}
|
||||
}
|
||||
auto crc_chance = options.find(CRC_CHECK_CHANCE);
|
||||
if (crc_chance != options.end()) {
|
||||
try {
|
||||
_crc_check_chance = std::stod(crc_chance->second);
|
||||
} catch (const std::exception& e) {
|
||||
throw exceptions::syntax_exception(sstring("Invalid double value ") + crc_chance->second + "for " + CRC_CHECK_CHANCE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
compressor get_compressor() const { return _compressor; }
|
||||
int32_t chunk_length() const { return _chunk_length.value_or(int(DEFAULT_CHUNK_LENGTH)); }
|
||||
double crc_check_chance() const { return _crc_check_chance.value_or(double(DEFAULT_CRC_CHECK_CHANCE)); }
|
||||
|
||||
void validate();
|
||||
std::map<sstring, sstring> get_options() const;
|
||||
bool operator==(const compression_parameters& other) const;
|
||||
bool operator!=(const compression_parameters& other) const;
|
||||
void validate() {
|
||||
if (_chunk_length) {
|
||||
auto chunk_length = _chunk_length.value();
|
||||
if (chunk_length <= 0) {
|
||||
throw exceptions::configuration_exception(sstring("Invalid negative or null ") + CHUNK_LENGTH_KB);
|
||||
}
|
||||
// _chunk_length must be a power of two
|
||||
if (chunk_length & (chunk_length - 1)) {
|
||||
throw exceptions::configuration_exception(sstring(CHUNK_LENGTH_KB) + " must be a power of 2.");
|
||||
}
|
||||
}
|
||||
if (_crc_check_chance && (_crc_check_chance.value() < 0.0 || _crc_check_chance.value() > 1.0)) {
|
||||
throw exceptions::configuration_exception(sstring(CRC_CHECK_CHANCE) + " must be between 0.0 and 1.0.");
|
||||
}
|
||||
}
|
||||
|
||||
std::map<sstring, sstring> get_options() const {
|
||||
if (_compressor == compressor::none) {
|
||||
return std::map<sstring, sstring>();
|
||||
}
|
||||
std::map<sstring, sstring> opts;
|
||||
opts.emplace(sstring(SSTABLE_COMPRESSION), compressor_name());
|
||||
if (_chunk_length) {
|
||||
opts.emplace(sstring(CHUNK_LENGTH_KB), std::to_string(_chunk_length.value() / 1024));
|
||||
}
|
||||
if (_crc_check_chance) {
|
||||
opts.emplace(sstring(CRC_CHECK_CHANCE), std::to_string(_crc_check_chance.value()));
|
||||
}
|
||||
return opts;
|
||||
}
|
||||
bool operator==(const compression_parameters& other) const {
|
||||
return _compressor == other._compressor
|
||||
&& _chunk_length == other._chunk_length
|
||||
&& _crc_check_chance == other._crc_check_chance;
|
||||
}
|
||||
bool operator!=(const compression_parameters& other) const {
|
||||
return !(*this == other);
|
||||
}
|
||||
private:
|
||||
void validate_options(const std::map<sstring, sstring>&);
|
||||
void validate_options(const std::map<sstring, sstring>& options) {
|
||||
// currently, there are no options specific to a particular compressor
|
||||
static std::set<sstring> keywords({
|
||||
sstring(SSTABLE_COMPRESSION),
|
||||
sstring(CHUNK_LENGTH_KB),
|
||||
sstring(CRC_CHECK_CHANCE),
|
||||
});
|
||||
for (auto&& opt : options) {
|
||||
if (!keywords.count(opt.first)) {
|
||||
throw exceptions::configuration_exception(sprint("Unknown compression option '%s'.", opt.first));
|
||||
}
|
||||
}
|
||||
}
|
||||
bool is_compressor_class(const sstring& value, const sstring& class_name) {
|
||||
static const sstring namespace_prefix = "org.apache.cassandra.io.compress.";
|
||||
return value == class_name || value == namespace_prefix + class_name;
|
||||
}
|
||||
sstring compressor_name() const {
|
||||
switch (_compressor) {
|
||||
case compressor::lz4:
|
||||
return "org.apache.cassandra.io.compress.LZ4Compressor";
|
||||
case compressor::snappy:
|
||||
return "org.apache.cassandra.io.compress.SnappyCompressor";
|
||||
case compressor::deflate:
|
||||
return "org.apache.cassandra.io.compress.DeflateCompressor";
|
||||
default:
|
||||
abort();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -107,6 +107,13 @@ native_transport_port: 9042
|
||||
# keeping native_transport_port unencrypted.
|
||||
#native_transport_port_ssl: 9142
|
||||
|
||||
# Throttles all outbound streaming file transfers on this node to the
|
||||
# given total throughput in Mbps. This is necessary because Scylla does
|
||||
# mostly sequential IO when streaming data during bootstrap or repair, which
|
||||
# can lead to saturating the network connection and degrading rpc performance.
|
||||
# When unset, the default is 200 Mbps or 25 MB/s.
|
||||
# stream_throughput_outbound_megabits_per_sec: 200
|
||||
|
||||
# How long the coordinator should wait for read operations to complete
|
||||
read_request_timeout_in_ms: 5000
|
||||
|
||||
@@ -240,8 +247,9 @@ batch_size_fail_threshold_in_kb: 50
|
||||
# Uncomment to enable experimental features
|
||||
# experimental: true
|
||||
|
||||
# The directory where hints files are stored if hinted handoff is enabled.
|
||||
# hints_directory: /var/lib/scylla/hints
|
||||
###################################################
|
||||
## Not currently supported, reserved for future use
|
||||
###################################################
|
||||
|
||||
# See http://wiki.apache.org/cassandra/HintedHandoff
|
||||
# May either be "true" or "false" to enable globally, or contain a list
|
||||
@@ -265,10 +273,6 @@ batch_size_fail_threshold_in_kb: 50
|
||||
# cross-dc handoff tends to be slower
|
||||
# max_hints_delivery_threads: 2
|
||||
|
||||
###################################################
|
||||
## Not currently supported, reserved for future use
|
||||
###################################################
|
||||
|
||||
# Maximum throttle in KBs per second, total. This will be
|
||||
# reduced proportionally to the number of nodes in the cluster.
|
||||
# batchlog_replay_throttle_in_kb: 1024
|
||||
|
||||
160
configure.py
160
configure.py
@@ -20,11 +20,9 @@
|
||||
# along with Scylla. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
|
||||
import os, os.path, textwrap, argparse, sys, shlex, subprocess, tempfile, re, platform
|
||||
import os, os.path, textwrap, argparse, sys, shlex, subprocess, tempfile, re
|
||||
from distutils.spawn import find_executable
|
||||
|
||||
tempfile.tempdir = "./build/tmp"
|
||||
|
||||
configure_args = str.join(' ', [shlex.quote(x) for x in sys.argv[1:]])
|
||||
|
||||
for line in open('/etc/os-release'):
|
||||
@@ -85,33 +83,17 @@ def pkg_config(option, package):
|
||||
return output.decode('utf-8').strip()
|
||||
|
||||
def try_compile(compiler, source = '', flags = []):
|
||||
return try_compile_and_link(compiler, source, flags = flags + ['-c'])
|
||||
|
||||
def ensure_tmp_dir_exists():
|
||||
if not os.path.exists(tempfile.tempdir):
|
||||
os.makedirs(tempfile.tempdir)
|
||||
|
||||
def try_compile_and_link(compiler, source = '', flags = []):
|
||||
ensure_tmp_dir_exists()
|
||||
with tempfile.NamedTemporaryFile() as sfile:
|
||||
ofile = tempfile.mktemp()
|
||||
try:
|
||||
sfile.file.write(bytes(source, 'utf-8'))
|
||||
sfile.file.flush()
|
||||
# We can't write to /dev/null, since in some cases (-ftest-coverage) gcc will create an auxiliary
|
||||
# output file based on the name of the output file, and "/dev/null.gcsa" is not a good name
|
||||
return subprocess.call([compiler, '-x', 'c++', '-o', ofile, sfile.name] + args.user_cflags.split() + flags,
|
||||
stdout = subprocess.DEVNULL,
|
||||
stderr = subprocess.DEVNULL) == 0
|
||||
finally:
|
||||
if os.path.exists(ofile):
|
||||
os.unlink(ofile)
|
||||
sfile.file.write(bytes(source, 'utf-8'))
|
||||
sfile.file.flush()
|
||||
return subprocess.call([compiler, '-x', 'c++', '-o', '/dev/null', '-c', sfile.name] + args.user_cflags.split() + flags,
|
||||
stdout = subprocess.DEVNULL,
|
||||
stderr = subprocess.DEVNULL) == 0
|
||||
|
||||
def flag_supported(flag, compiler):
|
||||
def warning_supported(warning, compiler):
|
||||
# gcc ignores -Wno-x even if it is not supported
|
||||
adjusted = re.sub('^-Wno-', '-W', flag)
|
||||
split = adjusted.split(' ')
|
||||
return try_compile(flags = ['-Werror'] + split, compiler = compiler)
|
||||
adjusted = re.sub('^-Wno-', '-W', warning)
|
||||
return try_compile(flags = ['-Werror', adjusted], compiler = compiler)
|
||||
|
||||
def debug_flag(compiler):
|
||||
src_with_auto = textwrap.dedent('''\
|
||||
@@ -126,14 +108,6 @@ def debug_flag(compiler):
|
||||
print('Note: debug information disabled; upgrade your compiler')
|
||||
return ''
|
||||
|
||||
def gold_supported(compiler):
|
||||
src_main = 'int main(int argc, char **argv) { return 0; }'
|
||||
if try_compile_and_link(source = src_main, flags = ['-fuse-ld=gold'], compiler = compiler):
|
||||
return '-fuse-ld=gold'
|
||||
else:
|
||||
print('Note: gold not found; using default system linker')
|
||||
return ''
|
||||
|
||||
def maybe_static(flag, libs):
|
||||
if flag and not args.static:
|
||||
libs = '-Wl,-Bstatic {} -Wl,-Bdynamic'.format(libs)
|
||||
@@ -159,13 +133,6 @@ class Thrift(object):
|
||||
def endswith(self, end):
|
||||
return self.source.endswith(end)
|
||||
|
||||
def default_target_arch():
|
||||
mach = platform.machine()
|
||||
if platform.machine() in ['i386', 'i686', 'x86_64']:
|
||||
return 'nehalem'
|
||||
else:
|
||||
return ''
|
||||
|
||||
class Antlr3Grammar(object):
|
||||
def __init__(self, source):
|
||||
self.source = source
|
||||
@@ -187,13 +154,13 @@ modes = {
|
||||
'debug': {
|
||||
'sanitize': '-fsanitize=address -fsanitize=leak -fsanitize=undefined',
|
||||
'sanitize_libs': '-lasan -lubsan',
|
||||
'opt': '-O0 -DDEBUG -DDEBUG_SHARED_PTR -DDEFAULT_ALLOCATOR -DDEBUG_LSA_SANITIZER',
|
||||
'opt': '-O0 -DDEBUG -DDEBUG_SHARED_PTR -DDEFAULT_ALLOCATOR',
|
||||
'libs': '',
|
||||
},
|
||||
'release': {
|
||||
'sanitize': '',
|
||||
'sanitize_libs': '',
|
||||
'opt': '-O3',
|
||||
'opt': '-O2',
|
||||
'libs': '',
|
||||
},
|
||||
}
|
||||
@@ -201,7 +168,7 @@ modes = {
|
||||
scylla_tests = [
|
||||
'tests/mutation_test',
|
||||
'tests/mvcc_test',
|
||||
'tests/mutation_fragment_test',
|
||||
'tests/streamed_mutation_test',
|
||||
'tests/flat_mutation_reader_test',
|
||||
'tests/schema_registry_test',
|
||||
'tests/canonical_mutation_test',
|
||||
@@ -211,7 +178,6 @@ scylla_tests = [
|
||||
'tests/partitioner_test',
|
||||
'tests/frozen_mutation_test',
|
||||
'tests/serialized_action_test',
|
||||
'tests/hint_test',
|
||||
'tests/clustering_ranges_walker_test',
|
||||
'tests/perf/perf_mutation',
|
||||
'tests/lsa_async_eviction_test',
|
||||
@@ -249,7 +215,6 @@ scylla_tests = [
|
||||
'tests/config_test',
|
||||
'tests/gossiping_property_file_snitch_test',
|
||||
'tests/ec2_snitch_test',
|
||||
'tests/gce_snitch_test',
|
||||
'tests/snitch_reset_test',
|
||||
'tests/network_topology_strategy_test',
|
||||
'tests/query_processor_test',
|
||||
@@ -271,11 +236,11 @@ scylla_tests = [
|
||||
'tests/database_test',
|
||||
'tests/nonwrapping_range_test',
|
||||
'tests/input_stream_test',
|
||||
'tests/sstable_atomic_deletion_test',
|
||||
'tests/virtual_reader_test',
|
||||
'tests/view_schema_test',
|
||||
'tests/counter_test',
|
||||
'tests/cell_locker_test',
|
||||
'tests/row_locker_test',
|
||||
'tests/streaming_histogram_test',
|
||||
'tests/duration_test',
|
||||
'tests/vint_serialization_test',
|
||||
@@ -285,26 +250,13 @@ scylla_tests = [
|
||||
'tests/castas_fcts_test',
|
||||
'tests/big_decimal_test',
|
||||
'tests/aggregate_fcts_test',
|
||||
'tests/role_manager_test',
|
||||
'tests/caching_options_test',
|
||||
'tests/auth_resource_test',
|
||||
'tests/cql_auth_query_test',
|
||||
'tests/enum_set_test',
|
||||
'tests/extensions_test',
|
||||
'tests/cql_auth_syntax_test',
|
||||
'tests/querier_cache',
|
||||
'tests/querier_cache_resource_based_eviction',
|
||||
]
|
||||
|
||||
perf_tests = [
|
||||
'tests/perf/perf_mutation_readers'
|
||||
]
|
||||
|
||||
apps = [
|
||||
'scylla',
|
||||
]
|
||||
|
||||
tests = scylla_tests + perf_tests
|
||||
tests = scylla_tests
|
||||
|
||||
other = [
|
||||
'iotune',
|
||||
@@ -326,8 +278,6 @@ arg_parser.add_argument('--cflags', action = 'store', dest = 'user_cflags', defa
|
||||
help = 'Extra flags for the C++ compiler')
|
||||
arg_parser.add_argument('--ldflags', action = 'store', dest = 'user_ldflags', default = '',
|
||||
help = 'Extra flags for the linker')
|
||||
arg_parser.add_argument('--target', action = 'store', dest = 'target', default = default_target_arch(),
|
||||
help = 'Target architecture (-march)')
|
||||
arg_parser.add_argument('--compiler', action = 'store', dest = 'cxx', default = 'g++',
|
||||
help = 'C++ compiler path')
|
||||
arg_parser.add_argument('--c-compiler', action='store', dest='cc', default='gcc',
|
||||
@@ -346,8 +296,6 @@ arg_parser.add_argument('--static-thrift', dest = 'staticthrift', action = 'stor
|
||||
help = 'Link libthrift statically')
|
||||
arg_parser.add_argument('--static-boost', dest = 'staticboost', action = 'store_true',
|
||||
help = 'Link boost statically')
|
||||
arg_parser.add_argument('--static-yaml-cpp', dest = 'staticyamlcpp', action = 'store_true',
|
||||
help = 'Link libyaml-cpp statically')
|
||||
arg_parser.add_argument('--tests-debuginfo', action = 'store', dest = 'tests_debuginfo', type = int, default = 0,
|
||||
help = 'Enable(1)/disable(0)compiler debug information generation for tests')
|
||||
arg_parser.add_argument('--python', action = 'store', dest = 'python', default = 'python3',
|
||||
@@ -372,7 +320,7 @@ scylla_core = (['database.cc',
|
||||
'schema_registry.cc',
|
||||
'bytes.cc',
|
||||
'mutation.cc',
|
||||
'mutation_fragment.cc',
|
||||
'streamed_mutation.cc',
|
||||
'partition_version.cc',
|
||||
'row_cache.cc',
|
||||
'canonical_mutation.cc',
|
||||
@@ -383,7 +331,6 @@ scylla_core = (['database.cc',
|
||||
'supervisor.cc',
|
||||
'utils/logalloc.cc',
|
||||
'utils/large_bitset.cc',
|
||||
'utils/buffer_input_stream.cc',
|
||||
'mutation_partition.cc',
|
||||
'mutation_partition_view.cc',
|
||||
'mutation_partition_serializer.cc',
|
||||
@@ -391,8 +338,7 @@ scylla_core = (['database.cc',
|
||||
'flat_mutation_reader.cc',
|
||||
'mutation_query.cc',
|
||||
'keys.cc',
|
||||
'counters.cc',
|
||||
'compress.cc',
|
||||
'counters.cc',
|
||||
'sstables/sstables.cc',
|
||||
'sstables/compress.cc',
|
||||
'sstables/row.cc',
|
||||
@@ -400,8 +346,8 @@ scylla_core = (['database.cc',
|
||||
'sstables/compaction.cc',
|
||||
'sstables/compaction_strategy.cc',
|
||||
'sstables/compaction_manager.cc',
|
||||
'sstables/atomic_deletion.cc',
|
||||
'sstables/integrity_checked_file_impl.cc',
|
||||
'sstables/prepended_input_stream.cc',
|
||||
'transport/event.cc',
|
||||
'transport/event_notifier.cc',
|
||||
'transport/server.cc',
|
||||
@@ -424,6 +370,7 @@ scylla_core = (['database.cc',
|
||||
'cql3/statements/create_table_statement.cc',
|
||||
'cql3/statements/create_view_statement.cc',
|
||||
'cql3/statements/create_type_statement.cc',
|
||||
'cql3/statements/create_user_statement.cc',
|
||||
'cql3/statements/drop_index_statement.cc',
|
||||
'cql3/statements/drop_keyspace_statement.cc',
|
||||
'cql3/statements/drop_table_statement.cc',
|
||||
@@ -445,6 +392,8 @@ scylla_core = (['database.cc',
|
||||
'cql3/statements/truncate_statement.cc',
|
||||
'cql3/statements/alter_table_statement.cc',
|
||||
'cql3/statements/alter_view_statement.cc',
|
||||
'cql3/statements/alter_user_statement.cc',
|
||||
'cql3/statements/drop_user_statement.cc',
|
||||
'cql3/statements/list_users_statement.cc',
|
||||
'cql3/statements/authorization_statement.cc',
|
||||
'cql3/statements/permission_altering_statement.cc',
|
||||
@@ -453,10 +402,9 @@ scylla_core = (['database.cc',
|
||||
'cql3/statements/revoke_statement.cc',
|
||||
'cql3/statements/alter_type_statement.cc',
|
||||
'cql3/statements/alter_keyspace_statement.cc',
|
||||
'cql3/statements/role-management-statements.cc',
|
||||
'cql3/update_parameters.cc',
|
||||
'cql3/ut_name.cc',
|
||||
'cql3/role_name.cc',
|
||||
'cql3/user_options.cc',
|
||||
'thrift/handler.cc',
|
||||
'thrift/server.cc',
|
||||
'thrift/thrift_validation.cc',
|
||||
@@ -498,16 +446,15 @@ scylla_core = (['database.cc',
|
||||
'db/commitlog/commitlog.cc',
|
||||
'db/commitlog/commitlog_replayer.cc',
|
||||
'db/commitlog/commitlog_entry.cc',
|
||||
'db/hints/manager.cc',
|
||||
'db/config.cc',
|
||||
'db/extensions.cc',
|
||||
'db/heat_load_balance.cc',
|
||||
'db/index/secondary_index.cc',
|
||||
'db/marshal/type_parser.cc',
|
||||
'db/batchlog_manager.cc',
|
||||
'db/view/view.cc',
|
||||
'db/view/row_locking.cc',
|
||||
'index/secondary_index_manager.cc',
|
||||
'io/io.cc',
|
||||
'utils/utils.cc',
|
||||
'utils/UUID_gen.cc',
|
||||
'utils/i_filter.cc',
|
||||
'utils/bloom_filter.cc',
|
||||
@@ -543,6 +490,7 @@ scylla_core = (['database.cc',
|
||||
'locator/network_topology_strategy.cc',
|
||||
'locator/everywhere_replication_strategy.cc',
|
||||
'locator/token_metadata.cc',
|
||||
'locator/locator.cc',
|
||||
'locator/snitch_base.cc',
|
||||
'locator/simple_snitch.cc',
|
||||
'locator/rack_inferring_snitch.cc',
|
||||
@@ -550,7 +498,6 @@ scylla_core = (['database.cc',
|
||||
'locator/production_snitch_base.cc',
|
||||
'locator/ec2_snitch.cc',
|
||||
'locator/ec2_multi_region_snitch.cc',
|
||||
'locator/gce_snitch.cc',
|
||||
'message/messaging_service.cc',
|
||||
'service/client_state.cc',
|
||||
'service/migration_task.cc',
|
||||
@@ -583,16 +530,12 @@ scylla_core = (['database.cc',
|
||||
'auth/authenticator.cc',
|
||||
'auth/common.cc',
|
||||
'auth/default_authorizer.cc',
|
||||
'auth/resource.cc',
|
||||
'auth/roles-metadata.cc',
|
||||
'auth/data_resource.cc',
|
||||
'auth/password_authenticator.cc',
|
||||
'auth/permission.cc',
|
||||
'auth/permissions_cache.cc',
|
||||
'auth/service.cc',
|
||||
'auth/standard_role_manager.cc',
|
||||
'auth/transitional.cc',
|
||||
'auth/authentication_options.cc',
|
||||
'auth/role_or_anonymous.cc',
|
||||
'tracing/tracing.cc',
|
||||
'tracing/trace_keyspace_helper.cc',
|
||||
'tracing/trace_state.cc',
|
||||
@@ -602,8 +545,6 @@ scylla_core = (['database.cc',
|
||||
'disk-error-handler.cc',
|
||||
'duration.cc',
|
||||
'vint-serialization.cc',
|
||||
'utils/arch/powerpc/crc32-vpmsum/crc32_wrapper.cc',
|
||||
'querier.cc',
|
||||
]
|
||||
+ [Antlr3Grammar('cql3/Cql.g')]
|
||||
+ [Thrift('interface/cassandra.thrift', 'Cassandra')]
|
||||
@@ -705,10 +646,6 @@ pure_boost_tests = set([
|
||||
'tests/compress_test',
|
||||
'tests/chunked_vector_test',
|
||||
'tests/big_decimal_test',
|
||||
'tests/caching_options_test',
|
||||
'tests/auth_resource_test',
|
||||
'tests/enum_set_test',
|
||||
'tests/cql_auth_syntax_test',
|
||||
])
|
||||
|
||||
tests_not_using_seastar_test_framework = set([
|
||||
@@ -727,7 +664,6 @@ tests_not_using_seastar_test_framework = set([
|
||||
'tests/memory_footprint',
|
||||
'tests/gossip',
|
||||
'tests/perf/perf_sstable',
|
||||
'tests/querier_cache_resource_based_eviction',
|
||||
]) | pure_boost_tests
|
||||
|
||||
for t in tests_not_using_seastar_test_framework:
|
||||
@@ -742,13 +678,6 @@ for t in scylla_tests:
|
||||
else:
|
||||
deps[t] += scylla_core + api + idls + ['tests/cql_test_env.cc']
|
||||
|
||||
perf_tests_seastar_deps = [
|
||||
'seastar/tests/perf/perf_tests.cc'
|
||||
]
|
||||
|
||||
for t in perf_tests:
|
||||
deps[t] = [t + '.cc'] + scylla_tests_dependencies + perf_tests_seastar_deps
|
||||
|
||||
deps['tests/sstable_test'] += ['tests/sstable_datafile_test.cc', 'tests/sstable_utils.cc']
|
||||
deps['tests/mutation_reader_test'] += ['tests/sstable_utils.cc']
|
||||
|
||||
@@ -779,20 +708,10 @@ warnings = [
|
||||
|
||||
warnings = [w
|
||||
for w in warnings
|
||||
if flag_supported(flag = w, compiler = args.cxx)]
|
||||
if warning_supported(warning = w, compiler = args.cxx)]
|
||||
|
||||
warnings = ' '.join(warnings + ['-Wno-error=deprecated-declarations'])
|
||||
|
||||
optimization_flags = [
|
||||
'--param inline-unit-growth=300',
|
||||
]
|
||||
optimization_flags = [o
|
||||
for o in optimization_flags
|
||||
if flag_supported(flag = o, compiler = args.cxx)]
|
||||
modes['release']['opt'] += ' ' + ' '.join(optimization_flags)
|
||||
|
||||
gold_linker_flag = gold_supported(compiler = args.cxx)
|
||||
|
||||
dbgflag = debug_flag(args.cxx) if args.debuginfo else ''
|
||||
tests_link_rule = 'link' if args.tests_debuginfo else 'link_stripped'
|
||||
|
||||
@@ -880,20 +799,14 @@ if args.staticcxx:
|
||||
seastar_flags += ['--static-stdc++']
|
||||
if args.staticboost:
|
||||
seastar_flags += ['--static-boost']
|
||||
if args.staticyamlcpp:
|
||||
seastar_flags += ['--static-yaml-cpp']
|
||||
if args.gcc6_concepts:
|
||||
seastar_flags += ['--enable-gcc6-concepts']
|
||||
if args.alloc_failure_injector:
|
||||
seastar_flags += ['--enable-alloc-failure-injector']
|
||||
|
||||
seastar_cflags = args.user_cflags
|
||||
if args.target != '':
|
||||
seastar_cflags += ' -march=' + args.target
|
||||
seastar_cflags = args.user_cflags + " -march=nehalem"
|
||||
seastar_ldflags = args.user_ldflags
|
||||
seastar_flags += ['--compiler', args.cxx, '--c-compiler', args.cc, '--cflags=%s' % (seastar_cflags), '--ldflags=%s' %(seastar_ldflags),
|
||||
'--c++-dialect=gnu++1z', '--optflags=%s' % (modes['release']['opt']),
|
||||
]
|
||||
seastar_flags += ['--compiler', args.cxx, '--c-compiler', args.cc, '--cflags=%s' % (seastar_cflags), '--ldflags=%s' %(seastar_ldflags)]
|
||||
|
||||
status = subprocess.call([python, './configure.py'] + seastar_flags, cwd = 'seastar')
|
||||
|
||||
@@ -924,16 +837,11 @@ for mode in build_modes:
|
||||
seastar_deps = 'practically_anything_can_change_so_lets_run_it_every_time_and_restat.'
|
||||
|
||||
args.user_cflags += " " + pkg_config("--cflags", "jsoncpp")
|
||||
libs = ' '.join([maybe_static(args.staticyamlcpp, '-lyaml-cpp'), '-llz4', '-lz', '-lsnappy', pkg_config("--libs", "jsoncpp"),
|
||||
maybe_static(args.staticboost, '-lboost_filesystem'), ' -lcrypt', ' -lcryptopp',
|
||||
libs = ' '.join(['-lyaml-cpp', '-llz4', '-lz', '-lsnappy', pkg_config("--libs", "jsoncpp"),
|
||||
maybe_static(args.staticboost, '-lboost_filesystem'), ' -lcrypt',
|
||||
maybe_static(args.staticboost, '-lboost_date_time'),
|
||||
])
|
||||
|
||||
xxhash_dir = 'xxHash'
|
||||
|
||||
if not os.path.exists(xxhash_dir) or not os.listdir(xxhash_dir):
|
||||
raise Exception(xxhash_dir + ' is empty. Run "git submodule update --init".')
|
||||
|
||||
if not args.staticboost:
|
||||
args.user_cflags += ' -DBOOST_TEST_DYN_LINK'
|
||||
|
||||
@@ -956,14 +864,13 @@ os.makedirs(outdir, exist_ok = True)
|
||||
do_sanitize = True
|
||||
if args.static:
|
||||
do_sanitize = False
|
||||
|
||||
with open(buildfile, 'w') as f:
|
||||
f.write(textwrap.dedent('''\
|
||||
configure_args = {configure_args}
|
||||
builddir = {outdir}
|
||||
cxx = {cxx}
|
||||
cxxflags = {user_cflags} {warnings} {defines}
|
||||
ldflags = {gold_linker_flag} {user_ldflags}
|
||||
ldflags = -fuse-ld=gold {user_ldflags}
|
||||
libs = {libs}
|
||||
pool link_pool
|
||||
depth = {link_pool_depth}
|
||||
@@ -992,7 +899,7 @@ with open(buildfile, 'w') as f:
|
||||
for mode in build_modes:
|
||||
modeval = modes[mode]
|
||||
f.write(textwrap.dedent('''\
|
||||
cxxflags_{mode} = {opt} -DXXH_PRIVATE_API -I. -I $builddir/{mode}/gen -I seastar -I seastar/build/{mode}/gen
|
||||
cxxflags_{mode} = -I. -I $builddir/{mode}/gen -I seastar -I seastar/build/{mode}/gen
|
||||
rule cxx.{mode}
|
||||
command = $cxx -MD -MT $out -MF $out.d {seastar_cflags} $cxxflags $cxxflags_{mode} $obj_cxxflags -c -o $out $in
|
||||
description = CXX $out
|
||||
@@ -1040,7 +947,6 @@ with open(buildfile, 'w') as f:
|
||||
objs = ['$builddir/' + mode + '/' + src.replace('.cc', '.o')
|
||||
for src in srcs
|
||||
if src.endswith('.cc')]
|
||||
objs.append('$builddir/../utils/arch/powerpc/crc32-vpmsum/crc32.S')
|
||||
has_thrift = False
|
||||
for dep in deps[binary]:
|
||||
if isinstance(dep, Thrift):
|
||||
@@ -1144,7 +1050,7 @@ with open(buildfile, 'w') as f:
|
||||
rule configure
|
||||
command = {python} configure.py $configure_args
|
||||
generator = 1
|
||||
build build.ninja: configure | configure.py seastar/configure.py
|
||||
build build.ninja: configure | configure.py
|
||||
rule cscope
|
||||
command = find -name '*.[chS]' -o -name "*.cc" -o -name "*.hh" | cscope -bq -i-
|
||||
description = CSCOPE
|
||||
|
||||
89
cpu_controller.hh
Normal file
89
cpu_controller.hh
Normal file
@@ -0,0 +1,89 @@
|
||||
/*
|
||||
* Copyright (C) 2017 ScyllaDB
|
||||
*/
|
||||
|
||||
/*
|
||||
* This file is part of Scylla.
|
||||
*
|
||||
* Scylla is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Scylla is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Scylla. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <seastar/core/thread.hh>
|
||||
#include <seastar/core/timer.hh>
|
||||
#include <chrono>
|
||||
|
||||
// Simple proportional controller to adjust shares of memtable/streaming flushes.
|
||||
//
|
||||
// Goal is to flush as fast as we can, but not so fast that we steal all the CPU from incoming
|
||||
// requests, and at the same time minimize user-visible fluctuations in the flush quota.
|
||||
//
|
||||
// What that translates to is we'll try to keep virtual dirty's firt derivative at 0 (IOW, we keep
|
||||
// virtual dirty constant), which means that the rate of incoming writes is equal to the rate of
|
||||
// flushed bytes.
|
||||
//
|
||||
// The exact point at which the controller stops determines the desired flush CPU usage. As we
|
||||
// approach the hard dirty limit, we need to be more aggressive. We will therefore define two
|
||||
// thresholds, and increase the constant as we cross them.
|
||||
//
|
||||
// 1) the soft limit line
|
||||
// 2) halfway between soft limit and dirty limit
|
||||
//
|
||||
// The constants q1 and q2 are used to determine the proportional factor at each stage.
|
||||
//
|
||||
// Below the soft limit, we are in no particular hurry to flush, since it means we're set to
|
||||
// complete flushing before we a new memtable is ready. The quota is dirty * q1, and q1 is set to a
|
||||
// low number.
|
||||
//
|
||||
// The first half of the virtual dirty region is where we expect to be usually, so we have a low
|
||||
// slope corresponding to a sluggish response between q1 * soft_limit and q2.
|
||||
//
|
||||
// In the second half, we're getting close to the hard dirty limit so we increase the slope and
|
||||
// become more responsive, up to a maximum quota of qmax.
|
||||
//
|
||||
// For now we'll just set them in the structure not to complicate the constructor. But q1, q2 and
|
||||
// qmax can easily become parameters if we find another user.
|
||||
class flush_cpu_controller {
|
||||
static constexpr float hard_dirty_limit = 0.50;
|
||||
static constexpr float q1 = 0.01;
|
||||
static constexpr float q2 = 0.2;
|
||||
static constexpr float qmax = 1;
|
||||
|
||||
float _current_quota = 0.0f;
|
||||
float _goal;
|
||||
std::function<float()> _current_dirty;
|
||||
std::chrono::milliseconds _interval;
|
||||
timer<> _update_timer;
|
||||
|
||||
seastar::thread_scheduling_group _scheduling_group;
|
||||
seastar::thread_scheduling_group *_current_scheduling_group = nullptr;
|
||||
|
||||
void adjust();
|
||||
public:
|
||||
seastar::thread_scheduling_group* scheduling_group() {
|
||||
return _current_scheduling_group;
|
||||
}
|
||||
float current_quota() const {
|
||||
return _current_quota;
|
||||
}
|
||||
|
||||
struct disabled {
|
||||
seastar::thread_scheduling_group *backup;
|
||||
};
|
||||
flush_cpu_controller(disabled d) : _scheduling_group(std::chrono::nanoseconds(0), 0), _current_scheduling_group(d.backup) {}
|
||||
flush_cpu_controller(std::chrono::milliseconds interval, float soft_limit, std::function<float()> current_dirty);
|
||||
flush_cpu_controller(flush_cpu_controller&&) = default;
|
||||
};
|
||||
|
||||
|
||||
219
cql3/Cql.g
219
cql3/Cql.g
@@ -56,16 +56,13 @@ options {
|
||||
#include "cql3/statements/index_prop_defs.hh"
|
||||
#include "cql3/statements/raw/use_statement.hh"
|
||||
#include "cql3/statements/raw/batch_statement.hh"
|
||||
#include "cql3/statements/create_user_statement.hh"
|
||||
#include "cql3/statements/alter_user_statement.hh"
|
||||
#include "cql3/statements/drop_user_statement.hh"
|
||||
#include "cql3/statements/list_users_statement.hh"
|
||||
#include "cql3/statements/grant_statement.hh"
|
||||
#include "cql3/statements/revoke_statement.hh"
|
||||
#include "cql3/statements/list_permissions_statement.hh"
|
||||
#include "cql3/statements/alter_role_statement.hh"
|
||||
#include "cql3/statements/list_roles_statement.hh"
|
||||
#include "cql3/statements/grant_role_statement.hh"
|
||||
#include "cql3/statements/revoke_role_statement.hh"
|
||||
#include "cql3/statements/drop_role_statement.hh"
|
||||
#include "cql3/statements/create_role_statement.hh"
|
||||
#include "cql3/statements/index_target.hh"
|
||||
#include "cql3/statements/ks_prop_defs.hh"
|
||||
#include "cql3/selection/raw_selector.hh"
|
||||
@@ -83,8 +80,6 @@ options {
|
||||
#include "cql3/maps.hh"
|
||||
#include "cql3/sets.hh"
|
||||
#include "cql3/lists.hh"
|
||||
#include "cql3/role_name.hh"
|
||||
#include "cql3/role_options.hh"
|
||||
#include "cql3/type_cast.hh"
|
||||
#include "cql3/tuples.hh"
|
||||
#include "cql3/user_types.hh"
|
||||
@@ -94,7 +89,6 @@ options {
|
||||
#include "core/sstring.hh"
|
||||
#include "CqlLexer.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <unordered_map>
|
||||
#include <map>
|
||||
}
|
||||
@@ -242,12 +236,6 @@ struct uninitialized {
|
||||
return res;
|
||||
}
|
||||
|
||||
bool convert_boolean_literal(stdx::string_view s) {
|
||||
std::string lower_s(s.size(), '\0');
|
||||
std::transform(s.cbegin(), s.cend(), lower_s.begin(), &::tolower);
|
||||
return lower_s == "true";
|
||||
}
|
||||
|
||||
void add_raw_update(std::vector<std::pair<::shared_ptr<cql3::column_identifier::raw>,::shared_ptr<cql3::operation::raw_update>>>& operations,
|
||||
::shared_ptr<cql3::column_identifier::raw> key, ::shared_ptr<cql3::operation::raw_update> update)
|
||||
{
|
||||
@@ -357,12 +345,6 @@ cqlStatement returns [shared_ptr<raw::parsed_statement> stmt]
|
||||
| st32=createViewStatement { $stmt = st32; }
|
||||
| st33=alterViewStatement { $stmt = st33; }
|
||||
| st34=dropViewStatement { $stmt = st34; }
|
||||
| st35=listRolesStatement { $stmt = st35; }
|
||||
| st36=grantRoleStatement { $stmt = st36; }
|
||||
| st37=revokeRoleStatement { $stmt = st37; }
|
||||
| st38=dropRoleStatement { $stmt = st38; }
|
||||
| st39=createRoleStatement { $stmt = st39; }
|
||||
| st40=alterRoleStatement { $stmt = st40; }
|
||||
;
|
||||
|
||||
/*
|
||||
@@ -387,6 +369,7 @@ selectStatement returns [shared_ptr<raw::select_statement> expr]
|
||||
}
|
||||
: K_SELECT ( ( K_DISTINCT { is_distinct = true; } )?
|
||||
sclause=selectClause
|
||||
| sclause=selectCountClause
|
||||
)
|
||||
K_FROM cf=columnFamilyName
|
||||
( K_WHERE wclause=whereClause )?
|
||||
@@ -413,7 +396,6 @@ selector returns [shared_ptr<raw_selector> s]
|
||||
unaliasedSelector returns [shared_ptr<selectable::raw> s]
|
||||
@init { shared_ptr<selectable::raw> tmp; }
|
||||
: ( c=cident { tmp = c; }
|
||||
| K_COUNT '(' countArgument ')' { tmp = selectable::with_function::raw::make_count_rows_function(); }
|
||||
| K_WRITETIME '(' c=cident ')' { tmp = make_shared<selectable::writetime_or_ttl::raw>(c, true); }
|
||||
| K_TTL '(' c=cident ')' { tmp = make_shared<selectable::writetime_or_ttl::raw>(c, false); }
|
||||
| f=functionName args=selectionFunctionArgs { tmp = ::make_shared<selectable::with_function::raw>(std::move(f), std::move(args)); }
|
||||
@@ -430,6 +412,16 @@ selectionFunctionArgs returns [std::vector<shared_ptr<selectable::raw>> a]
|
||||
')'
|
||||
;
|
||||
|
||||
selectCountClause returns [std::vector<shared_ptr<raw_selector>> expr]
|
||||
@init{ auto alias = make_shared<cql3::column_identifier>("count", false); }
|
||||
: K_COUNT '(' countArgument ')' (K_AS c=ident { alias = c; })? {
|
||||
auto&& with_fn = ::make_shared<cql3::selection::selectable::with_function::raw>(
|
||||
cql3::functions::function_name::native_function("countRows"),
|
||||
std::vector<shared_ptr<cql3::selection::selectable::raw>>());
|
||||
$expr.push_back(make_shared<cql3::selection::raw_selector>(with_fn, alias));
|
||||
}
|
||||
;
|
||||
|
||||
countArgument
|
||||
: '*'
|
||||
| i=INTEGER { if (i->getText() != "1") {
|
||||
@@ -983,7 +975,7 @@ truncateStatement returns [::shared_ptr<truncate_statement> stmt]
|
||||
;
|
||||
|
||||
/**
|
||||
* GRANT <permission> ON <resource> TO <grantee>
|
||||
* GRANT <permission> ON <resource> TO <username>
|
||||
*/
|
||||
grantStatement returns [::shared_ptr<grant_statement> stmt]
|
||||
: K_GRANT
|
||||
@@ -991,12 +983,12 @@ grantStatement returns [::shared_ptr<grant_statement> stmt]
|
||||
K_ON
|
||||
resource
|
||||
K_TO
|
||||
grantee=userOrRoleName
|
||||
{ $stmt = ::make_shared<grant_statement>($permissionOrAll.perms, $resource.res, std::move(grantee)); }
|
||||
username
|
||||
{ $stmt = ::make_shared<grant_statement>($permissionOrAll.perms, $resource.res, $username.text); }
|
||||
;
|
||||
|
||||
/**
|
||||
* REVOKE <permission> ON <resource> FROM <revokee>
|
||||
* REVOKE <permission> ON <resource> FROM <username>
|
||||
*/
|
||||
revokeStatement returns [::shared_ptr<revoke_statement> stmt]
|
||||
: K_REVOKE
|
||||
@@ -1004,104 +996,80 @@ revokeStatement returns [::shared_ptr<revoke_statement> stmt]
|
||||
K_ON
|
||||
resource
|
||||
K_FROM
|
||||
revokee=userOrRoleName
|
||||
{ $stmt = ::make_shared<revoke_statement>($permissionOrAll.perms, $resource.res, std::move(revokee)); }
|
||||
;
|
||||
|
||||
/**
|
||||
* GRANT <rolename> to <grantee>
|
||||
*/
|
||||
grantRoleStatement returns [::shared_ptr<grant_role_statement> stmt]
|
||||
: K_GRANT role=userOrRoleName K_TO grantee=userOrRoleName
|
||||
{ $stmt = ::make_shared<grant_role_statement>(std::move(role), std::move(grantee)); }
|
||||
;
|
||||
|
||||
/**
|
||||
* REVOKE <rolename> FROM <revokee>
|
||||
*/
|
||||
revokeRoleStatement returns [::shared_ptr<revoke_role_statement> stmt]
|
||||
: K_REVOKE role=userOrRoleName K_FROM revokee=userOrRoleName
|
||||
{ $stmt = ::make_shared<revoke_role_statement>(std::move(role), std::move(revokee)); }
|
||||
username
|
||||
{ $stmt = ::make_shared<revoke_statement>($permissionOrAll.perms, $resource.res, $username.text); }
|
||||
;
|
||||
|
||||
listPermissionsStatement returns [::shared_ptr<list_permissions_statement> stmt]
|
||||
@init {
|
||||
std::optional<auth::resource> r;
|
||||
std::optional<sstring> role;
|
||||
std::experimental::optional<auth::data_resource> r;
|
||||
std::experimental::optional<sstring> u;
|
||||
bool recursive = true;
|
||||
}
|
||||
: K_LIST
|
||||
permissionOrAll
|
||||
( K_ON resource { r = $resource.res; } )?
|
||||
( K_OF rn=userOrRoleName { role = sstring(static_cast<cql3::role_name>(rn).to_string()); } )?
|
||||
( K_OF username { u = sstring($username.text); } )?
|
||||
( K_NORECURSIVE { recursive = false; } )?
|
||||
{ $stmt = ::make_shared<list_permissions_statement>($permissionOrAll.perms, std::move(r), std::move(role), recursive); }
|
||||
{ $stmt = ::make_shared<list_permissions_statement>($permissionOrAll.perms, std::move(r), std::move(u), recursive); }
|
||||
;
|
||||
|
||||
permission returns [auth::permission perm]
|
||||
: p=(K_CREATE | K_ALTER | K_DROP | K_SELECT | K_MODIFY | K_AUTHORIZE | K_DESCRIBE)
|
||||
: p=(K_CREATE | K_ALTER | K_DROP | K_SELECT | K_MODIFY | K_AUTHORIZE)
|
||||
{ $perm = auth::permissions::from_string($p.text); }
|
||||
;
|
||||
|
||||
permissionOrAll returns [auth::permission_set perms]
|
||||
: K_ALL ( K_PERMISSIONS )? { $perms = auth::permissions::ALL; }
|
||||
: K_ALL ( K_PERMISSIONS )? { $perms = auth::permissions::ALL_DATA; }
|
||||
| p=permission ( K_PERMISSION )? { $perms = auth::permission_set::from_mask(auth::permission_set::mask_for($p.perm)); }
|
||||
;
|
||||
|
||||
resource returns [uninitialized<auth::resource> res]
|
||||
: d=dataResource { $res = std::move(d); }
|
||||
| r=roleResource { $res = std::move(r); }
|
||||
resource returns [auth::data_resource res]
|
||||
: r=dataResource { $res = $r.res; }
|
||||
;
|
||||
|
||||
dataResource returns [uninitialized<auth::resource> res]
|
||||
: K_ALL K_KEYSPACES { $res = auth::resource(auth::resource_kind::data); }
|
||||
| K_KEYSPACE ks = keyspaceName { $res = auth::make_data_resource($ks.id); }
|
||||
dataResource returns [auth::data_resource res]
|
||||
: K_ALL K_KEYSPACES { $res = auth::data_resource(); }
|
||||
| K_KEYSPACE ks = keyspaceName { $res = auth::data_resource($ks.id); }
|
||||
| ( K_COLUMNFAMILY )? cf = columnFamilyName
|
||||
{ $res = auth::make_data_resource($cf.name->get_keyspace(), $cf.name->get_column_family()); }
|
||||
;
|
||||
|
||||
roleResource returns [uninitialized<auth::resource> res]
|
||||
: K_ALL K_ROLES { $res = auth::resource(auth::resource_kind::role); }
|
||||
| K_ROLE role = userOrRoleName { $res = auth::make_role_resource(static_cast<const cql3::role_name&>(role).to_string()); }
|
||||
{ $res = auth::data_resource($cf.name->get_keyspace(), $cf.name->get_column_family()); }
|
||||
;
|
||||
|
||||
/**
|
||||
* CREATE USER [IF NOT EXISTS] <username> [WITH PASSWORD <password>] [SUPERUSER|NOSUPERUSER]
|
||||
*/
|
||||
createUserStatement returns [::shared_ptr<create_role_statement> stmt]
|
||||
createUserStatement returns [::shared_ptr<create_user_statement> stmt]
|
||||
@init {
|
||||
cql3::role_options opts;
|
||||
opts.is_superuser = false;
|
||||
opts.can_login = true;
|
||||
|
||||
auto opts = ::make_shared<cql3::user_options>();
|
||||
bool superuser = false;
|
||||
bool ifNotExists = false;
|
||||
}
|
||||
: K_CREATE K_USER (K_IF K_NOT K_EXISTS { ifNotExists = true; })? username
|
||||
( K_WITH K_PASSWORD v=STRING_LITERAL { opts.password = $v.text; })?
|
||||
( K_SUPERUSER { opts.is_superuser = true; } | K_NOSUPERUSER { opts.is_superuser = false; } )?
|
||||
{ $stmt = ::make_shared<create_role_statement>(cql3::role_name($username.text, cql3::preserve_role_case::yes), std::move(opts), ifNotExists); }
|
||||
( K_WITH userOptions[opts] )?
|
||||
( K_SUPERUSER { superuser = true; } | K_NOSUPERUSER { superuser = false; } )?
|
||||
{ $stmt = ::make_shared<create_user_statement>($username.text, std::move(opts), superuser, ifNotExists); }
|
||||
;
|
||||
|
||||
/**
|
||||
* ALTER USER <username> [WITH PASSWORD <password>] [SUPERUSER|NOSUPERUSER]
|
||||
*/
|
||||
alterUserStatement returns [::shared_ptr<alter_role_statement> stmt]
|
||||
alterUserStatement returns [::shared_ptr<alter_user_statement> stmt]
|
||||
@init {
|
||||
cql3::role_options opts;
|
||||
auto opts = ::make_shared<cql3::user_options>();
|
||||
std::experimental::optional<bool> superuser;
|
||||
}
|
||||
: K_ALTER K_USER username
|
||||
( K_WITH K_PASSWORD v=STRING_LITERAL { opts.password = $v.text; })?
|
||||
( K_SUPERUSER { opts.is_superuser = true; } | K_NOSUPERUSER { opts.is_superuser = false; } )?
|
||||
{ $stmt = ::make_shared<alter_role_statement>(cql3::role_name($username.text, cql3::preserve_role_case::yes), std::move(opts)); }
|
||||
( K_WITH userOptions[opts] )?
|
||||
( K_SUPERUSER { superuser = true; } | K_NOSUPERUSER { superuser = false; } )?
|
||||
{ $stmt = ::make_shared<alter_user_statement>($username.text, std::move(opts), std::move(superuser)); }
|
||||
;
|
||||
|
||||
/**
|
||||
* DROP USER [IF EXISTS] <username>
|
||||
*/
|
||||
dropUserStatement returns [::shared_ptr<drop_role_statement> stmt]
|
||||
dropUserStatement returns [::shared_ptr<drop_user_statement> stmt]
|
||||
@init { bool ifExists = false; }
|
||||
: K_DROP K_USER (K_IF K_EXISTS { ifExists = true; })? username
|
||||
{ $stmt = ::make_shared<drop_role_statement>(cql3::role_name($username.text, cql3::preserve_role_case::yes), ifExists); }
|
||||
: K_DROP K_USER (K_IF K_EXISTS { ifExists = true; })? username { $stmt = ::make_shared<drop_user_statement>($username.text, ifExists); }
|
||||
;
|
||||
|
||||
/**
|
||||
@@ -1111,67 +1079,12 @@ listUsersStatement returns [::shared_ptr<list_users_statement> stmt]
|
||||
: K_LIST K_USERS { $stmt = ::make_shared<list_users_statement>(); }
|
||||
;
|
||||
|
||||
/**
|
||||
* CREATE ROLE [IF NOT EXISTS] <role_name> [WITH <roleOption> [AND <roleOption>]*]
|
||||
*/
|
||||
createRoleStatement returns [::shared_ptr<create_role_statement> stmt]
|
||||
@init {
|
||||
cql3::role_options opts;
|
||||
opts.is_superuser = false;
|
||||
opts.can_login = false;
|
||||
bool if_not_exists = false;
|
||||
}
|
||||
: K_CREATE K_ROLE (K_IF K_NOT K_EXISTS { if_not_exists = true; })? name=userOrRoleName
|
||||
(K_WITH roleOptions[opts])?
|
||||
{ $stmt = ::make_shared<create_role_statement>(name, std::move(opts), if_not_exists); }
|
||||
userOptions[::shared_ptr<cql3::user_options> opts]
|
||||
: userOption[opts]
|
||||
;
|
||||
|
||||
/**
|
||||
* ALTER ROLE <rolename> [WITH <roleOption> [AND <roleOption>]*]
|
||||
*/
|
||||
alterRoleStatement returns [::shared_ptr<alter_role_statement> stmt]
|
||||
@init {
|
||||
cql3::role_options opts;
|
||||
}
|
||||
: K_ALTER K_ROLE name=userOrRoleName
|
||||
(K_WITH roleOptions[opts])?
|
||||
{ $stmt = ::make_shared<alter_role_statement>(name, std::move(opts)); }
|
||||
;
|
||||
|
||||
/**
|
||||
* DROP ROLE [IF EXISTS] <rolename>
|
||||
*/
|
||||
dropRoleStatement returns [::shared_ptr<drop_role_statement> stmt]
|
||||
@init {
|
||||
bool if_exists = false;
|
||||
}
|
||||
: K_DROP K_ROLE (K_IF K_EXISTS { if_exists = true; })? name=userOrRoleName
|
||||
{ $stmt = ::make_shared<drop_role_statement>(name, if_exists); }
|
||||
;
|
||||
|
||||
/**
|
||||
* LIST ROLES [OF <rolename>] [NORECURSIVE]
|
||||
*/
|
||||
listRolesStatement returns [::shared_ptr<list_roles_statement> stmt]
|
||||
@init {
|
||||
bool recursive = true;
|
||||
std::optional<cql3::role_name> grantee;
|
||||
}
|
||||
: K_LIST K_ROLES
|
||||
(K_OF g=userOrRoleName { grantee = std::move(g); })?
|
||||
(K_NORECURSIVE { recursive = false; })?
|
||||
{ $stmt = ::make_shared<list_roles_statement>(grantee, recursive); }
|
||||
;
|
||||
|
||||
roleOptions[cql3::role_options& opts]
|
||||
: roleOption[opts] (K_AND roleOption[opts])*
|
||||
;
|
||||
|
||||
roleOption[cql3::role_options& opts]
|
||||
: K_PASSWORD '=' v=STRING_LITERAL { opts.password = $v.text; }
|
||||
| K_OPTIONS '=' m=mapLiteral { opts.options = convert_property_map(m); }
|
||||
| K_SUPERUSER '=' b=BOOLEAN { opts.is_superuser = convert_boolean_literal($b.text); }
|
||||
| K_LOGIN '=' b=BOOLEAN { opts.can_login = convert_boolean_literal($b.text); }
|
||||
userOption[::shared_ptr<cql3::user_options> opts]
|
||||
: k=K_PASSWORD v=STRING_LITERAL { opts->put($k.text, $v.text); }
|
||||
;
|
||||
|
||||
/** DEFINITIONS **/
|
||||
@@ -1212,13 +1125,12 @@ userTypeName returns [uninitialized<cql3::ut_name> name]
|
||||
: (ks=ident '.')? ut=non_type_ident { $name = cql3::ut_name(ks, ut); }
|
||||
;
|
||||
|
||||
userOrRoleName returns [uninitialized<cql3::role_name> name]
|
||||
: t=IDENT { $name = cql3::role_name($t.text, cql3::preserve_role_case::no); }
|
||||
| t=STRING_LITERAL { $name = cql3::role_name($t.text, cql3::preserve_role_case::yes); }
|
||||
| t=QUOTED_NAME { $name = cql3::role_name($t.text, cql3::preserve_role_case::yes); }
|
||||
| k=unreserved_keyword { $name = cql3::role_name(k, cql3::preserve_role_case::no); }
|
||||
| QMARK {add_recognition_error("Bind variables cannot be used for role names");}
|
||||
#if 0
|
||||
userOrRoleName returns [RoleName name]
|
||||
@init { $name = new RoleName(); }
|
||||
: roleName[name] {return $name;}
|
||||
;
|
||||
#endif
|
||||
|
||||
ksName[::shared_ptr<cql3::keyspace_element_name> name]
|
||||
: t=IDENT { $name->set_keyspace($t.text, false);}
|
||||
@@ -1241,6 +1153,15 @@ idxName[::shared_ptr<cql3::index_name> name]
|
||||
| QMARK {add_recognition_error("Bind variables cannot be used for index names");}
|
||||
;
|
||||
|
||||
#if 0
|
||||
roleName[RoleName name]
|
||||
: t=IDENT { $name.setName($t.text, false); }
|
||||
| t=QUOTED_NAME { $name.setName($t.text, true); }
|
||||
| k=unreserved_keyword { $name.setName(k, false); }
|
||||
| QMARK {addRecognitionError("Bind variables cannot be used for role names");}
|
||||
;
|
||||
#endif
|
||||
|
||||
constant returns [shared_ptr<cql3::constants::literal> constant]
|
||||
@init{std::string sign;}
|
||||
: t=STRING_LITERAL { $constant = cql3::constants::literal::string(sstring{$t.text}); }
|
||||
@@ -1585,7 +1506,6 @@ tuple_type returns [shared_ptr<cql3::cql3_type::raw> t]
|
||||
username
|
||||
: IDENT
|
||||
| STRING_LITERAL
|
||||
| QUOTED_NAME { add_recognition_error("Quoted strings are not supported for user names"); }
|
||||
;
|
||||
|
||||
// Basically the same as cident, but we need to exlude existing CQL3 types
|
||||
@@ -1624,13 +1544,8 @@ basic_unreserved_keyword returns [sstring str]
|
||||
| K_ALL
|
||||
| K_USER
|
||||
| K_USERS
|
||||
| K_ROLE
|
||||
| K_ROLES
|
||||
| K_SUPERUSER
|
||||
| K_NOSUPERUSER
|
||||
| K_LOGIN
|
||||
| K_NOLOGIN
|
||||
| K_OPTIONS
|
||||
| K_PASSWORD
|
||||
| K_EXISTS
|
||||
| K_CUSTOM
|
||||
@@ -1722,19 +1637,13 @@ K_OF: O F;
|
||||
K_REVOKE: R E V O K E;
|
||||
K_MODIFY: M O D I F Y;
|
||||
K_AUTHORIZE: A U T H O R I Z E;
|
||||
K_DESCRIBE: D E S C R I B E;
|
||||
K_NORECURSIVE: N O R E C U R S I V E;
|
||||
|
||||
K_USER: U S E R;
|
||||
K_USERS: U S E R S;
|
||||
K_ROLE: R O L E;
|
||||
K_ROLES: R O L E S;
|
||||
K_SUPERUSER: S U P E R U S E R;
|
||||
K_NOSUPERUSER: N O S U P E R U S E R;
|
||||
K_PASSWORD: P A S S W O R D;
|
||||
K_LOGIN: L O G I N;
|
||||
K_NOLOGIN: N O L O G I N;
|
||||
K_OPTIONS: O P T I O N S;
|
||||
|
||||
K_CLUSTERING: C L U S T E R I N G;
|
||||
K_ASCII: A S C I I;
|
||||
|
||||
@@ -123,7 +123,7 @@ public:
|
||||
// This is a workaround for antlr3 not distinguishing between
|
||||
// calling in lexer setText() with an empty string and not calling
|
||||
// setText() at all.
|
||||
if (text.size() == 1 && text[0] == '\xFF') {
|
||||
if (text.size() == 1 && text[0] == -1) {
|
||||
text.reset();
|
||||
}
|
||||
return ::make_shared<literal>(type::STRING, text);
|
||||
|
||||
@@ -67,12 +67,6 @@ class error_collector : public error_listener<RecognizerType, ExceptionBaseType>
|
||||
*/
|
||||
const sstring_view _query;
|
||||
|
||||
/**
|
||||
* An empty bitset to be used as a workaround for AntLR null dereference
|
||||
* bug.
|
||||
*/
|
||||
static typename ExceptionBaseType::BitsetListType _empty_bit_list;
|
||||
|
||||
public:
|
||||
|
||||
/**
|
||||
@@ -150,14 +144,6 @@ private:
|
||||
break;
|
||||
}
|
||||
default:
|
||||
// AntLR Exception class has a bug of dereferencing a null
|
||||
// pointer in the displayRecognitionError. The following
|
||||
// if statement makes sure it will not be null before the
|
||||
// call to that function (displayRecognitionError).
|
||||
// bug reference: https://github.com/antlr/antlr3/issues/191
|
||||
if (!ex->get_expectingSet()) {
|
||||
ex->set_expectingSet(&_empty_bit_list);
|
||||
}
|
||||
ex->displayRecognitionError(token_names, msg);
|
||||
}
|
||||
return msg.str();
|
||||
@@ -359,8 +345,4 @@ private:
|
||||
#endif
|
||||
};
|
||||
|
||||
template<typename RecognizerType, typename TokenType, typename ExceptionBaseType>
|
||||
typename ExceptionBaseType::BitsetListType
|
||||
error_collector<RecognizerType,TokenType,ExceptionBaseType>::_empty_bit_list = typename ExceptionBaseType::BitsetListType();
|
||||
|
||||
}
|
||||
|
||||
@@ -90,10 +90,6 @@ public:
|
||||
return false;
|
||||
}
|
||||
|
||||
virtual sstring column_name(const std::vector<sstring>& column_names) override {
|
||||
return sprint("%s(%s)", _name, join(", ", column_names));
|
||||
}
|
||||
|
||||
virtual void print(std::ostream& os) const override;
|
||||
};
|
||||
|
||||
|
||||
@@ -67,19 +67,6 @@ public:
|
||||
}
|
||||
};
|
||||
|
||||
static const sstring COUNT_ROWS_FUNCTION_NAME = "countRows";
|
||||
|
||||
class count_rows_function final : public native_aggregate_function {
|
||||
public:
|
||||
count_rows_function() : native_aggregate_function(COUNT_ROWS_FUNCTION_NAME, long_type, {}) {}
|
||||
virtual std::unique_ptr<aggregate> new_aggregate() override {
|
||||
return std::make_unique<impl_count_function>();
|
||||
}
|
||||
virtual sstring column_name(const std::vector<sstring>& column_names) override {
|
||||
return "count";
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* The function used to count the number of rows of a result set. This function is called when COUNT(*) or COUNT(1)
|
||||
* is specified.
|
||||
@@ -87,7 +74,7 @@ public:
|
||||
inline
|
||||
shared_ptr<aggregate_function>
|
||||
make_count_rows_function() {
|
||||
return make_shared<count_rows_function>();
|
||||
return make_native_aggregate_function_using<impl_count_function>("countRows", long_type);
|
||||
}
|
||||
|
||||
template <typename Type>
|
||||
@@ -227,29 +214,9 @@ make_avg_function() {
|
||||
return make_shared<avg_function_for<Type>>();
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
struct aggregate_type_for {
|
||||
using type = T;
|
||||
};
|
||||
|
||||
template<>
|
||||
struct aggregate_type_for<simple_date_native_type> {
|
||||
using type = simple_date_native_type::primary_type;
|
||||
};
|
||||
|
||||
template<>
|
||||
struct aggregate_type_for<timestamp_native_type> {
|
||||
using type = timestamp_native_type::primary_type;
|
||||
};
|
||||
|
||||
template<>
|
||||
struct aggregate_type_for<timeuuid_native_type> {
|
||||
using type = timeuuid_native_type::primary_type;
|
||||
};
|
||||
|
||||
template <typename Type>
|
||||
class impl_max_function_for final : public aggregate_function::aggregate {
|
||||
std::experimental::optional<typename aggregate_type_for<Type>::type> _max{};
|
||||
std::experimental::optional<Type> _max{};
|
||||
public:
|
||||
virtual void reset() override {
|
||||
_max = {};
|
||||
@@ -258,13 +225,13 @@ public:
|
||||
if (!_max) {
|
||||
return {};
|
||||
}
|
||||
return data_type_for<Type>()->decompose(Type{*_max});
|
||||
return data_type_for<Type>()->decompose(*_max);
|
||||
}
|
||||
virtual void add_input(cql_serialization_format sf, const std::vector<opt_bytes>& values) override {
|
||||
if (!values[0]) {
|
||||
return;
|
||||
}
|
||||
auto val = value_cast<typename aggregate_type_for<Type>::type>(data_type_for<Type>()->deserialize(*values[0]));
|
||||
auto val = value_cast<Type>(data_type_for<Type>()->deserialize(*values[0]));
|
||||
if (!_max) {
|
||||
_max = val;
|
||||
} else {
|
||||
@@ -296,7 +263,7 @@ make_max_function() {
|
||||
|
||||
template <typename Type>
|
||||
class impl_min_function_for final : public aggregate_function::aggregate {
|
||||
std::experimental::optional<typename aggregate_type_for<Type>::type> _min{};
|
||||
std::experimental::optional<Type> _min{};
|
||||
public:
|
||||
virtual void reset() override {
|
||||
_min = {};
|
||||
@@ -305,13 +272,13 @@ public:
|
||||
if (!_min) {
|
||||
return {};
|
||||
}
|
||||
return data_type_for<Type>()->decompose(Type{*_min});
|
||||
return data_type_for<Type>()->decompose(*_min);
|
||||
}
|
||||
virtual void add_input(cql_serialization_format sf, const std::vector<opt_bytes>& values) override {
|
||||
if (!values[0]) {
|
||||
return;
|
||||
}
|
||||
auto val = value_cast<typename aggregate_type_for<Type>::type>(data_type_for<Type>()->deserialize(*values[0]));
|
||||
auto val = value_cast<Type>(data_type_for<Type>()->deserialize(*values[0]));
|
||||
if (!_min) {
|
||||
_min = val;
|
||||
} else {
|
||||
|
||||
@@ -81,15 +81,6 @@ public:
|
||||
virtual void print(std::ostream& os) const = 0;
|
||||
virtual bool uses_function(const sstring& ks_name, const sstring& function_name) = 0;
|
||||
virtual bool has_reference_to(function& f) = 0;
|
||||
|
||||
/**
|
||||
* Returns the name of the function to use within a ResultSet.
|
||||
*
|
||||
* @param column_names the names of the columns used to call the function
|
||||
* @return the name of the function to use within a ResultSet
|
||||
*/
|
||||
virtual sstring column_name(const std::vector<sstring>& column_names) = 0;
|
||||
|
||||
friend class function_call;
|
||||
friend std::ostream& operator<<(std::ostream& os, const function& f);
|
||||
};
|
||||
|
||||
@@ -42,16 +42,10 @@
|
||||
#pragma once
|
||||
|
||||
#include "core/sstring.hh"
|
||||
#include "seastarx.hh"
|
||||
#include "db/system_keyspace.hh"
|
||||
#include <iosfwd>
|
||||
#include <functional>
|
||||
|
||||
namespace db {
|
||||
|
||||
sstring system_keyspace_name();
|
||||
|
||||
}
|
||||
|
||||
namespace cql3 {
|
||||
|
||||
namespace functions {
|
||||
@@ -62,7 +56,7 @@ public:
|
||||
sstring name;
|
||||
|
||||
static function_name native_function(sstring name) {
|
||||
return function_name(db::system_keyspace_name(), name);
|
||||
return function_name(db::system_keyspace::NAME, name);
|
||||
}
|
||||
|
||||
function_name() = default; // for ANTLR
|
||||
|
||||
@@ -95,15 +95,6 @@ functions::init() {
|
||||
declare(aggregate_fcts::make_max_function<sstring>());
|
||||
declare(aggregate_fcts::make_min_function<sstring>());
|
||||
|
||||
declare(aggregate_fcts::make_max_function<simple_date_native_type>());
|
||||
declare(aggregate_fcts::make_min_function<simple_date_native_type>());
|
||||
|
||||
declare(aggregate_fcts::make_max_function<timestamp_native_type>());
|
||||
declare(aggregate_fcts::make_min_function<timestamp_native_type>());
|
||||
|
||||
declare(aggregate_fcts::make_max_function<timeuuid_native_type>());
|
||||
declare(aggregate_fcts::make_min_function<timeuuid_native_type>());
|
||||
|
||||
//FIXME:
|
||||
//declare(aggregate_fcts::make_count_function<bytes>());
|
||||
//declare(aggregate_fcts::make_max_function<bytes>());
|
||||
|
||||
@@ -64,5 +64,23 @@ public:
|
||||
}
|
||||
};
|
||||
|
||||
template <class Aggregate>
|
||||
class native_aggregate_function_using : public native_aggregate_function {
|
||||
public:
|
||||
native_aggregate_function_using(sstring name, data_type type)
|
||||
: native_aggregate_function(std::move(name), type, {}) {
|
||||
}
|
||||
virtual std::unique_ptr<aggregate> new_aggregate() override {
|
||||
return std::make_unique<Aggregate>();
|
||||
}
|
||||
};
|
||||
|
||||
template <class Aggregate>
|
||||
shared_ptr<native_aggregate_function>
|
||||
make_native_aggregate_function_using(sstring name, data_type type) {
|
||||
return ::make_shared<native_aggregate_function_using<Aggregate>>(name, type);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -202,6 +202,12 @@ lists::delayed_value::bind(const query_options& options) {
|
||||
if (bo.is_unset_value()) {
|
||||
return constants::UNSET_VALUE;
|
||||
}
|
||||
// We don't support value > 64K because the serialization format encode the length as an unsigned short.
|
||||
if (bo->size() > std::numeric_limits<uint16_t>::max()) {
|
||||
throw exceptions::invalid_request_exception(sprint("List value is too long. List values are limited to %d bytes but %d bytes value provided",
|
||||
std::numeric_limits<uint16_t>::max(),
|
||||
bo->size()));
|
||||
}
|
||||
|
||||
buffers.push_back(std::move(to_bytes(*bo)));
|
||||
}
|
||||
@@ -299,6 +305,11 @@ lists::setter_by_index::execute(mutation& m, const clustering_key_prefix& prefix
|
||||
if (!value) {
|
||||
mut.cells.emplace_back(eidx, params.make_dead_cell());
|
||||
} else {
|
||||
if (value->size() > std::numeric_limits<uint16_t>::max()) {
|
||||
throw exceptions::invalid_request_exception(
|
||||
sprint("List value is too long. List values are limited to %d bytes but %d bytes value provided",
|
||||
std::numeric_limits<uint16_t>::max(), value->size()));
|
||||
}
|
||||
mut.cells.emplace_back(eidx, params.make_cell(*value));
|
||||
}
|
||||
auto smut = ltype->serialize_mutation_form(mut);
|
||||
|
||||
11
cql3/maps.cc
11
cql3/maps.cc
@@ -245,6 +245,11 @@ maps::delayed_value::bind(const query_options& options) {
|
||||
if (value_bytes.is_unset_value()) {
|
||||
return constants::UNSET_VALUE;
|
||||
}
|
||||
if (value_bytes->size() > std::numeric_limits<uint16_t>::max()) {
|
||||
throw exceptions::invalid_request_exception(sprint("Map value is too long. Map values are limited to %d bytes but %d bytes value provided",
|
||||
std::numeric_limits<uint16_t>::max(),
|
||||
value_bytes->size()));
|
||||
}
|
||||
buffers.emplace(std::move(to_bytes(*key_bytes)), std::move(to_bytes(*value_bytes)));
|
||||
}
|
||||
return ::make_shared<value>(std::move(buffers));
|
||||
@@ -295,6 +300,12 @@ maps::setter_by_key::execute(mutation& m, const clustering_key_prefix& prefix, c
|
||||
if (!key) {
|
||||
throw invalid_request_exception("Invalid null map key");
|
||||
}
|
||||
if (value && value->size() >= std::numeric_limits<uint16_t>::max()) {
|
||||
throw invalid_request_exception(
|
||||
sprint("Map value is too long. Map values are limited to %d bytes but %d bytes value provided",
|
||||
std::numeric_limits<uint16_t>::max(),
|
||||
value->size()));
|
||||
}
|
||||
auto avalue = value ? params.make_cell(*value) : params.make_dead_cell();
|
||||
map_type_impl::mutation update = { {}, { { std::move(to_bytes(*key)), std::move(avalue) } } };
|
||||
// should have been verified as map earlier?
|
||||
|
||||
@@ -71,11 +71,10 @@ public:
|
||||
virtual std::vector<bytes_opt> values(const query_options& options) const override {
|
||||
auto src = values_as_keys(options);
|
||||
std::vector<bytes_opt> res;
|
||||
for (const clustering_key_prefix& r : src) {
|
||||
for (const auto& component : r.components()) {
|
||||
res.emplace_back(component);
|
||||
}
|
||||
}
|
||||
std::transform(src.begin(), src.end(), std::back_inserter(res), [this] (auto&& r) {
|
||||
auto view = r.representation();
|
||||
return bytes(view.begin(), view.end());
|
||||
});
|
||||
return res;
|
||||
}
|
||||
|
||||
|
||||
@@ -66,8 +66,6 @@ public:
|
||||
*/
|
||||
virtual std::vector<const column_definition*> get_column_defs() const = 0;
|
||||
|
||||
virtual std::vector<bytes_opt> values(const query_options& options) const = 0;
|
||||
|
||||
/**
|
||||
* Returns <code>true</code> if one of the restrictions use the specified function.
|
||||
*
|
||||
|
||||
@@ -305,11 +305,10 @@ public:
|
||||
std::vector<bytes_opt> values(const query_options& options) const override {
|
||||
auto src = values_as_keys(options);
|
||||
std::vector<bytes_opt> res;
|
||||
for (const ValueType& r : src) {
|
||||
for (const auto& component : r.components()) {
|
||||
res.emplace_back(component);
|
||||
}
|
||||
}
|
||||
std::transform(src.begin(), src.end(), std::back_inserter(res), [this](const ValueType & r) {
|
||||
auto view = r.representation();
|
||||
return bytes(view.begin(), view.end());
|
||||
});
|
||||
return res;
|
||||
}
|
||||
std::vector<bytes_opt> bounds(statements::bound b, const query_options& options) const override {
|
||||
|
||||
@@ -102,15 +102,6 @@ public:
|
||||
return r;
|
||||
}
|
||||
|
||||
virtual std::vector<bytes_opt> values(const query_options& options) const override {
|
||||
std::vector<bytes_opt> r;
|
||||
for (auto&& e : _restrictions) {
|
||||
auto&& value = e.second->value(options);
|
||||
r.emplace_back(value);
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the restriction associated to the specified column.
|
||||
*
|
||||
|
||||
@@ -327,7 +327,20 @@ private:
|
||||
checkNotNull(value, "Invalid null token value");
|
||||
return p.getTokenFactory().fromByteArray(value);
|
||||
}
|
||||
#endif
|
||||
|
||||
public:
|
||||
/**
|
||||
* Checks if the query does not contains any restriction on the clustering columns.
|
||||
*
|
||||
* @return <code>true</code> if the query does not contains any restriction on the clustering columns,
|
||||
* <code>false</code> otherwise.
|
||||
*/
|
||||
bool has_no_clustering_columns_restriction() const {
|
||||
return _clustering_columns_restrictions->empty();
|
||||
}
|
||||
|
||||
#if 0
|
||||
// For non-composite slices, we don't support internally the difference between exclusive and
|
||||
// inclusive bounds, so we deal with it manually.
|
||||
bool is_non_composite_slice_with_exclusive_bounds()
|
||||
@@ -371,15 +384,6 @@ public:
|
||||
return !_clustering_columns_restrictions->empty();
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the restrictions contain any non-primary key restrictions
|
||||
*
|
||||
* @return <code>true</code> if the restrictions contain any non-primary key restrictions, <code>false</code> otherwise.
|
||||
*/
|
||||
bool has_non_primary_key_restriction() const {
|
||||
return !_nonprimary_key_restrictions->empty();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return true if column is restricted by some restriction, false otherwise
|
||||
*/
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <map>
|
||||
#include <optional>
|
||||
|
||||
#include <seastar/core/sstring.hh>
|
||||
|
||||
#include "seastarx.hh"
|
||||
|
||||
namespace cql3 {
|
||||
|
||||
struct role_options final {
|
||||
std::optional<bool> is_superuser{};
|
||||
std::optional<bool> can_login{};
|
||||
std::optional<sstring> password{};
|
||||
|
||||
// The parser makes a `std::map`, not a `std::unordered_map`.
|
||||
std::optional<std::map<sstring, sstring>> options{};
|
||||
};
|
||||
|
||||
}
|
||||
@@ -69,7 +69,7 @@ abstract_function_selector::new_factory(shared_ptr<functions::function> fun, sha
|
||||
}
|
||||
|
||||
virtual sstring column_name() override {
|
||||
return _fun->column_name(_factories->get_column_names());
|
||||
return sprint("%s(%s)", _fun->name(), join(", ", _factories->get_column_names()));
|
||||
}
|
||||
|
||||
virtual data_type get_return_type() override {
|
||||
|
||||
@@ -26,7 +26,6 @@
|
||||
#include "selector_factories.hh"
|
||||
#include "cql3/functions/functions.hh"
|
||||
#include "cql3/functions/castas_fcts.hh"
|
||||
#include "cql3/functions/aggregate_fcts.hh"
|
||||
#include "abstract_function_selector.hh"
|
||||
#include "writetime_or_ttl_selector.hh"
|
||||
|
||||
@@ -105,13 +104,6 @@ selectable::with_function::raw::processes_selection() const {
|
||||
return true;
|
||||
}
|
||||
|
||||
shared_ptr<selectable::with_function::raw>
|
||||
selectable::with_function::raw::make_count_rows_function() {
|
||||
return ::make_shared<cql3::selection::selectable::with_function::raw>(
|
||||
cql3::functions::function_name::native_function(cql3::functions::aggregate_fcts::COUNT_ROWS_FUNCTION_NAME),
|
||||
std::vector<shared_ptr<cql3::selection::selectable::raw>>());
|
||||
}
|
||||
|
||||
shared_ptr<selector::factory>
|
||||
selectable::with_field_selection::new_selector_factory(database& db, schema_ptr s, std::vector<const column_definition*>& defs) {
|
||||
auto&& factory = _selected->new_selector_factory(db, s, defs);
|
||||
|
||||
@@ -110,11 +110,9 @@ public:
|
||||
}
|
||||
virtual shared_ptr<selectable> prepare(schema_ptr s) override;
|
||||
virtual bool processes_selection() const override;
|
||||
static ::shared_ptr<selectable::with_function::raw> make_count_rows_function();
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
class selectable::with_cast : public selectable {
|
||||
::shared_ptr<selectable> _arg;
|
||||
::shared_ptr<cql3_type> _type;
|
||||
|
||||
@@ -105,11 +105,9 @@ public:
|
||||
virtual void reset() = 0;
|
||||
|
||||
virtual assignment_testable::test_result test_assignment(database& db, const sstring& keyspace, ::shared_ptr<column_specification> receiver) override {
|
||||
auto t1 = receiver->type->underlying_type();
|
||||
auto t2 = get_type()->underlying_type();
|
||||
if (t1 == t2) {
|
||||
if (receiver->type == get_type()) {
|
||||
return assignment_testable::test_result::EXACT_MATCH;
|
||||
} else if (t1->is_value_compatible_with(*t2)) {
|
||||
} else if (receiver->type->is_value_compatible_with(*get_type())) {
|
||||
return assignment_testable::test_result::WEAKLY_ASSIGNABLE;
|
||||
} else {
|
||||
return assignment_testable::test_result::NOT_ASSIGNABLE;
|
||||
|
||||
@@ -42,10 +42,9 @@
|
||||
#include "alter_keyspace_statement.hh"
|
||||
#include "prepared_statement.hh"
|
||||
#include "service/migration_manager.hh"
|
||||
#include "db/system_keyspace.hh"
|
||||
#include "database.hh"
|
||||
|
||||
bool is_system_keyspace(const sstring& keyspace);
|
||||
|
||||
cql3::statements::alter_keyspace_statement::alter_keyspace_statement(sstring name, ::shared_ptr<ks_prop_defs> attrs)
|
||||
: _name(name)
|
||||
, _attrs(std::move(attrs))
|
||||
|
||||
@@ -185,9 +185,6 @@ future<shared_ptr<cql_transport::event::schema_change>> alter_table_statement::a
|
||||
column_name = _raw_column_name->prepare_column_identifier(schema);
|
||||
def = get_column_definition(schema, *column_name);
|
||||
}
|
||||
if (_properties->get_id()) {
|
||||
throw exceptions::configuration_exception("Cannot alter table id.");
|
||||
}
|
||||
|
||||
auto& cf = db.find_column_family(schema);
|
||||
std::vector<view_ptr> view_updates;
|
||||
@@ -322,7 +319,7 @@ future<shared_ptr<cql_transport::event::schema_change>> alter_table_statement::a
|
||||
throw exceptions::invalid_request_exception("ALTER COLUMNFAMILY WITH invoked, but no parameters found");
|
||||
}
|
||||
|
||||
_properties->validate(db.get_config().extensions());
|
||||
_properties->validate();
|
||||
|
||||
if (!cf.views().empty() && _properties->get_gc_grace_seconds() == 0) {
|
||||
throw exceptions::invalid_request_exception(
|
||||
@@ -337,7 +334,7 @@ future<shared_ptr<cql_transport::event::schema_change>> alter_table_statement::a
|
||||
throw exceptions::invalid_request_exception("Cannot set default_time_to_live on a table with counters");
|
||||
}
|
||||
|
||||
_properties->apply_to_builder(cfm, db.get_config().extensions());
|
||||
_properties->apply_to_builder(cfm);
|
||||
break;
|
||||
|
||||
case alter_table_statement::type::rename:
|
||||
|
||||
116
cql3/statements/alter_user_statement.cc
Normal file
116
cql3/statements/alter_user_statement.cc
Normal file
@@ -0,0 +1,116 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Copyright 2016 ScyllaDB
|
||||
*
|
||||
* Modified by ScyllaDB
|
||||
*/
|
||||
|
||||
/*
|
||||
* This file is part of Scylla.
|
||||
*
|
||||
* Scylla is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Scylla is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Scylla. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <boost/range/adaptor/map.hpp>
|
||||
|
||||
#include "alter_user_statement.hh"
|
||||
#include "auth/authenticator.hh"
|
||||
#include "auth/service.hh"
|
||||
|
||||
cql3::statements::alter_user_statement::alter_user_statement(sstring username, ::shared_ptr<user_options> opts, std::experimental::optional<bool> superuser)
|
||||
: _username(std::move(username))
|
||||
, _opts(std::move(opts))
|
||||
, _superuser(std::move(superuser))
|
||||
{}
|
||||
|
||||
void cql3::statements::alter_user_statement::validate(distributed<service::storage_proxy>& proxy, const service::client_state& state) {
|
||||
_opts->validate(state.get_auth_service()->underlying_authenticator());
|
||||
|
||||
if (!_superuser && _opts->empty()) {
|
||||
throw exceptions::invalid_request_exception("ALTER USER can't be empty");
|
||||
}
|
||||
|
||||
// validate login here before checkAccess to avoid leaking user existence to anonymous users.
|
||||
state.ensure_not_anonymous();
|
||||
|
||||
// cannot validate user existence here, because
|
||||
// we need to query -> continuation, and this is not a continuation method
|
||||
}
|
||||
|
||||
future<> cql3::statements::alter_user_statement::check_access(const service::client_state& state) {
|
||||
auto user = state.user();
|
||||
if (_superuser && user->name() == _username) {
|
||||
// using contractions in error messages is the ultimate sign of lowbrowness.
|
||||
// however, dtests depend on matching the exception messages. So we keep them despite
|
||||
// my disgust.
|
||||
throw exceptions::unauthorized_exception("You aren't allowed to alter your own superuser status");
|
||||
}
|
||||
|
||||
const auto& auth_service = *state.get_auth_service();
|
||||
|
||||
return auth::is_super_user(auth_service, *user).then([this, user, &auth_service](bool is_super) {
|
||||
if (_superuser && !is_super) {
|
||||
throw exceptions::unauthorized_exception("Only superusers are allowed to alter superuser status");
|
||||
}
|
||||
|
||||
if (!is_super && user->name() != _username) {
|
||||
throw exceptions::unauthorized_exception("You aren't allowed to alter this user");
|
||||
}
|
||||
|
||||
if (!is_super) {
|
||||
for (auto o : _opts->options() | boost::adaptors::map_keys) {
|
||||
if (!auth_service.underlying_authenticator().alterable_options().contains(o)) {
|
||||
throw exceptions::unauthorized_exception(sprint("You aren't allowed to alter {} option", o));
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
future<::shared_ptr<cql_transport::messages::result_message>>
|
||||
cql3::statements::alter_user_statement::execute(distributed<service::storage_proxy>& proxy, service::query_state& state, const query_options& options) {
|
||||
auto& client_state = state.get_client_state();
|
||||
auto& auth_service = *client_state.get_auth_service();
|
||||
|
||||
return auth_service.is_existing_user(_username).then([this, &auth_service](bool exists) {
|
||||
if (!exists) {
|
||||
throw exceptions::invalid_request_exception(sprint("User %s doesn't exist", _username));
|
||||
}
|
||||
auto f = _opts->options().empty() ? make_ready_future() : auth_service.underlying_authenticator().alter(_username, _opts->options());
|
||||
if (_superuser) {
|
||||
f = f.then([this, &auth_service] {
|
||||
return auth_service.insert_user(_username, *_superuser);
|
||||
});
|
||||
}
|
||||
return f.then([] { return make_ready_future<::shared_ptr<cql_transport::messages::result_message>>(); });
|
||||
});
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
*/
|
||||
|
||||
/*
|
||||
* Copyright 2017 ScyllaDB
|
||||
* Copyright 2016 ScyllaDB
|
||||
*
|
||||
* Modified by ScyllaDB
|
||||
*/
|
||||
@@ -41,33 +41,30 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <seastar/core/sstring.hh>
|
||||
#include <experimental/optional>
|
||||
|
||||
#include "cql3/statements/authentication_statement.hh"
|
||||
#include "cql3/role_name.hh"
|
||||
#include "cql3/role_options.hh"
|
||||
#include "authentication_statement.hh"
|
||||
#include "cql3/user_options.hh"
|
||||
|
||||
namespace cql3 {
|
||||
|
||||
namespace statements {
|
||||
|
||||
class alter_role_statement final : public authentication_statement {
|
||||
sstring _role;
|
||||
|
||||
role_options _options;
|
||||
|
||||
class alter_user_statement : public authentication_statement {
|
||||
private:
|
||||
sstring _username;
|
||||
::shared_ptr<user_options> _opts;
|
||||
std::experimental::optional<bool> _superuser;
|
||||
public:
|
||||
alter_role_statement(const cql3::role_name& name, const role_options& options)
|
||||
: _role(name.to_string())
|
||||
, _options(std::move(options)) {
|
||||
}
|
||||
|
||||
alter_user_statement(sstring, ::shared_ptr<user_options>, std::experimental::optional<bool> superuser = {});
|
||||
|
||||
void validate(distributed<service::storage_proxy>&, const service::client_state&) override;
|
||||
future<> check_access(const service::client_state&) override;
|
||||
|
||||
virtual future<> check_access(const service::client_state&) override;
|
||||
|
||||
virtual future<::shared_ptr<cql_transport::messages::result_message>>
|
||||
execute(distributed<service::storage_proxy>&, service::query_state&, const query_options&) override;
|
||||
future<::shared_ptr<cql_transport::messages::result_message>> execute(distributed<service::storage_proxy>&
|
||||
, service::query_state&
|
||||
, const query_options&) override;
|
||||
};
|
||||
|
||||
}
|
||||
@@ -44,7 +44,6 @@
|
||||
#include "service/migration_manager.hh"
|
||||
#include "validation.hh"
|
||||
#include "view_info.hh"
|
||||
#include "db/config.hh"
|
||||
|
||||
namespace cql3 {
|
||||
|
||||
@@ -86,10 +85,10 @@ future<shared_ptr<cql_transport::event::schema_change>> alter_view_statement::an
|
||||
throw exceptions::invalid_request_exception("ALTER MATERIALIZED VIEW WITH invoked, but no parameters found");
|
||||
}
|
||||
|
||||
_properties->validate(proxy.local().get_db().local().get_config().extensions());
|
||||
_properties->validate();
|
||||
|
||||
auto builder = schema_builder(schema);
|
||||
_properties->apply_to_builder(builder, proxy.local().get_db().local().get_config().extensions());
|
||||
_properties->apply_to_builder(builder);
|
||||
|
||||
if (builder.get_gc_grace_seconds() == 0) {
|
||||
throw exceptions::invalid_request_exception(
|
||||
|
||||
@@ -82,15 +82,9 @@ future<::shared_ptr<cql_transport::messages::result_message>> cql3::statements::
|
||||
throw std::runtime_error("unsupported operation");
|
||||
}
|
||||
|
||||
void cql3::statements::authorization_statement::maybe_correct_resource(auth::resource& resource, const service::client_state& state) {
|
||||
if (resource.kind() == auth::resource_kind::data) {
|
||||
const auto data_view = auth::data_resource_view(resource);
|
||||
const auto keyspace = data_view.keyspace();
|
||||
const auto table = data_view.table();
|
||||
|
||||
if (table && keyspace->empty()) {
|
||||
resource = auth::make_data_resource(state.get_keyspace(), *table);
|
||||
}
|
||||
void cql3::statements::authorization_statement::mayme_correct_resource(auth::data_resource& resource, const service::client_state& state) {
|
||||
if (resource.is_column_family_level() && resource.keyspace().empty()) {
|
||||
resource = auth::data_resource(state.get_keyspace(), resource.column_family());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -47,7 +47,7 @@
|
||||
#include "transport/messages_fwd.hh"
|
||||
|
||||
namespace auth {
|
||||
class resource;
|
||||
class data_resource;
|
||||
}
|
||||
|
||||
namespace cql3 {
|
||||
@@ -74,7 +74,7 @@ public:
|
||||
execute_internal(distributed<service::storage_proxy>& proxy, service::query_state& state, const query_options& options) override;
|
||||
|
||||
protected:
|
||||
static void maybe_correct_resource(auth::resource&, const service::client_state&);
|
||||
static void mayme_correct_resource(auth::data_resource&, const service::client_state&);
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -40,7 +40,6 @@
|
||||
*/
|
||||
|
||||
#include "cql3/statements/cf_prop_defs.hh"
|
||||
#include "db/extensions.hh"
|
||||
|
||||
#include <boost/algorithm/string/predicate.hpp>
|
||||
|
||||
@@ -66,13 +65,11 @@ const sstring cf_prop_defs::KW_COMPACTION = "compaction";
|
||||
const sstring cf_prop_defs::KW_COMPRESSION = "compression";
|
||||
const sstring cf_prop_defs::KW_CRC_CHECK_CHANCE = "crc_check_chance";
|
||||
|
||||
const sstring cf_prop_defs::KW_ID = "id";
|
||||
|
||||
const sstring cf_prop_defs::COMPACTION_STRATEGY_CLASS_KEY = "class";
|
||||
|
||||
const sstring cf_prop_defs::COMPACTION_ENABLED_KEY = "enabled";
|
||||
|
||||
void cf_prop_defs::validate(const db::extensions& exts) {
|
||||
void cf_prop_defs::validate() {
|
||||
// Skip validation if the comapction strategy class is already set as it means we've alreayd
|
||||
// prepared (and redoing it would set strategyClass back to null, which we don't want)
|
||||
if (_compaction_strategy_class) {
|
||||
@@ -84,20 +81,14 @@ void cf_prop_defs::validate(const db::extensions& exts) {
|
||||
KW_GCGRACESECONDS, KW_CACHING, KW_DEFAULT_TIME_TO_LIVE,
|
||||
KW_MIN_INDEX_INTERVAL, KW_MAX_INDEX_INTERVAL, KW_SPECULATIVE_RETRY,
|
||||
KW_BF_FP_CHANCE, KW_MEMTABLE_FLUSH_PERIOD, KW_COMPACTION,
|
||||
KW_COMPRESSION, KW_CRC_CHECK_CHANCE, KW_ID
|
||||
KW_COMPRESSION, KW_CRC_CHECK_CHANCE
|
||||
});
|
||||
static std::set<sstring> obsolete_keywords({
|
||||
sstring("index_interval"),
|
||||
sstring("replicate_on_write"),
|
||||
sstring("populate_io_cache_on_flush"),
|
||||
});
|
||||
property_definitions::validate(keywords, exts.schema_extension_keywords(), obsolete_keywords);
|
||||
|
||||
try {
|
||||
get_id();
|
||||
} catch(...) {
|
||||
std::throw_with_nested(exceptions::configuration_exception("Invalid table id"));
|
||||
}
|
||||
property_definitions::validate(keywords, obsolete_keywords);
|
||||
|
||||
auto compaction_options = get_compaction_options();
|
||||
if (!compaction_options.empty()) {
|
||||
@@ -163,16 +154,7 @@ int32_t cf_prop_defs::get_gc_grace_seconds() const
|
||||
return get_int(KW_GCGRACESECONDS, DEFAULT_GC_GRACE_SECONDS);
|
||||
}
|
||||
|
||||
stdx::optional<utils::UUID> cf_prop_defs::get_id() const {
|
||||
auto id = get_simple(KW_ID);
|
||||
if (id) {
|
||||
return utils::UUID(*id);
|
||||
}
|
||||
|
||||
return stdx::nullopt;
|
||||
}
|
||||
|
||||
void cf_prop_defs::apply_to_builder(schema_builder& builder, const db::extensions& exts) {
|
||||
void cf_prop_defs::apply_to_builder(schema_builder& builder) {
|
||||
if (has_property(KW_COMMENT)) {
|
||||
builder.set_comment(get_string(KW_COMMENT, ""));
|
||||
}
|
||||
@@ -250,15 +232,6 @@ void cf_prop_defs::apply_to_builder(schema_builder& builder, const db::extension
|
||||
if (cachingOptions != null)
|
||||
cfm.caching(cachingOptions);
|
||||
#endif
|
||||
|
||||
schema::extensions_map er;
|
||||
for (auto& p : exts.schema_extensions()) {
|
||||
auto i = _properties.find(p.first);
|
||||
if (i != _properties.end()) {
|
||||
std::visit([&](auto& v) { er.emplace(p.first, p.second(v)); }, i->second);
|
||||
}
|
||||
}
|
||||
builder.set_extensions(std::move(er));
|
||||
}
|
||||
|
||||
void cf_prop_defs::validate_minimum_int(const sstring& field, int32_t minimum_value, int32_t default_value) const
|
||||
|
||||
@@ -47,11 +47,6 @@
|
||||
#include "database.hh"
|
||||
#include "schema_builder.hh"
|
||||
#include "compaction_strategy.hh"
|
||||
#include "utils/UUID.hh"
|
||||
|
||||
namespace db {
|
||||
class extensions;
|
||||
}
|
||||
|
||||
namespace cql3 {
|
||||
|
||||
@@ -77,8 +72,6 @@ public:
|
||||
static const sstring KW_COMPRESSION;
|
||||
static const sstring KW_CRC_CHECK_CHANCE;
|
||||
|
||||
static const sstring KW_ID;
|
||||
|
||||
static const sstring COMPACTION_STRATEGY_CLASS_KEY;
|
||||
static const sstring COMPACTION_ENABLED_KEY;
|
||||
|
||||
@@ -89,7 +82,7 @@ public:
|
||||
private:
|
||||
std::experimental::optional<sstables::compaction_strategy_type> _compaction_strategy_class;
|
||||
public:
|
||||
void validate(const db::extensions&);
|
||||
void validate();
|
||||
std::map<sstring, sstring> get_compaction_options() const;
|
||||
stdx::optional<std::map<sstring, sstring>> get_compression_options() const;
|
||||
#if 0
|
||||
@@ -111,9 +104,8 @@ public:
|
||||
#endif
|
||||
int32_t get_default_time_to_live() const;
|
||||
int32_t get_gc_grace_seconds() const;
|
||||
stdx::optional<utils::UUID> get_id() const;
|
||||
|
||||
void apply_to_builder(schema_builder& builder, const db::extensions&);
|
||||
void apply_to_builder(schema_builder& builder);
|
||||
void validate_minimum_int(const sstring& field, int32_t minimum_value, int32_t default_value) const;
|
||||
};
|
||||
|
||||
|
||||
@@ -93,8 +93,8 @@ public:
|
||||
_defined_ordering.emplace_back(alias, reversed);
|
||||
}
|
||||
|
||||
void validate(const db::extensions& exts) {
|
||||
_properties->validate(exts);
|
||||
void validate() {
|
||||
_properties->validate();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -46,8 +46,6 @@
|
||||
|
||||
#include <regex>
|
||||
|
||||
bool is_system_keyspace(const sstring& keyspace);
|
||||
|
||||
namespace cql3 {
|
||||
|
||||
namespace statements {
|
||||
@@ -128,17 +126,6 @@ cql3::statements::create_keyspace_statement::prepare(database& db, cql_stats& st
|
||||
return std::make_unique<prepared_statement>(make_shared<create_keyspace_statement>(*this));
|
||||
}
|
||||
|
||||
future<> cql3::statements::create_keyspace_statement::grant_permissions_to_creator(const service::client_state& cs) {
|
||||
return do_with(auth::make_data_resource(keyspace()), [&cs](const auth::resource& r) {
|
||||
return auth::grant_applicable_permissions(
|
||||
*cs.get_auth_service(),
|
||||
*cs.user(),
|
||||
r).handle_exception_type([](const auth::unsupported_authorization_operation&) {
|
||||
// Nothing.
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -84,8 +84,6 @@ public:
|
||||
virtual future<shared_ptr<cql_transport::event::schema_change>> announce_migration(distributed<service::storage_proxy>& proxy, bool is_local_only) override;
|
||||
|
||||
virtual std::unique_ptr<prepared> prepare(database& db, cql_stats& stats) override;
|
||||
|
||||
virtual future<> grant_permissions_to_creator(const service::client_state&) override;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -1,85 +0,0 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Copyright 2017 ScyllaDB
|
||||
*
|
||||
* Modified by ScyllaDB
|
||||
*/
|
||||
|
||||
/*
|
||||
* This file is part of Scylla.
|
||||
*
|
||||
* Scylla is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Scylla is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Scylla. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <seastar/core/sstring.hh>
|
||||
|
||||
#include "cql3/statements/authentication_statement.hh"
|
||||
#include "cql3/role_name.hh"
|
||||
#include "cql3/role_options.hh"
|
||||
|
||||
namespace cql3 {
|
||||
|
||||
namespace statements {
|
||||
|
||||
class create_role_statement final : public authentication_statement {
|
||||
sstring _role;
|
||||
|
||||
bool _is_superuser;
|
||||
|
||||
bool _can_login;
|
||||
|
||||
role_options _options;
|
||||
|
||||
bool _if_not_exists;
|
||||
|
||||
public:
|
||||
create_role_statement(
|
||||
const cql3::role_name& name, const role_options& options, bool if_not_exists)
|
||||
: _role(name.to_string())
|
||||
, _options(std::move(options))
|
||||
, _if_not_exists(if_not_exists) {
|
||||
}
|
||||
|
||||
future<> grant_permissions_to_creator(const service::client_state&) const;
|
||||
|
||||
void validate(distributed<service::storage_proxy>&, const service::client_state&) override;
|
||||
|
||||
virtual future<> check_access(const service::client_state&) override;
|
||||
|
||||
virtual future<::shared_ptr<cql_transport::messages::result_message>>
|
||||
execute(distributed<service::storage_proxy>&, service::query_state&, const query_options&) override;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -49,8 +49,6 @@
|
||||
#include "cql3/statements/create_table_statement.hh"
|
||||
#include "cql3/statements/prepared_statement.hh"
|
||||
|
||||
#include "auth/resource.hh"
|
||||
#include "auth/service.hh"
|
||||
#include "schema_builder.hh"
|
||||
#include "service/storage_service.hh"
|
||||
|
||||
@@ -61,14 +59,11 @@ namespace statements {
|
||||
create_table_statement::create_table_statement(::shared_ptr<cf_name> name,
|
||||
::shared_ptr<cf_prop_defs> properties,
|
||||
bool if_not_exists,
|
||||
column_set_type static_columns,
|
||||
const stdx::optional<utils::UUID>& id)
|
||||
column_set_type static_columns)
|
||||
: schema_altering_statement{name}
|
||||
, _use_compact_storage(false)
|
||||
, _static_columns{static_columns}
|
||||
, _properties{properties}
|
||||
, _if_not_exists{if_not_exists}
|
||||
, _id(id)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -95,8 +90,8 @@ std::vector<column_definition> create_table_statement::get_columns()
|
||||
}
|
||||
|
||||
future<shared_ptr<cql_transport::event::schema_change>> create_table_statement::announce_migration(distributed<service::storage_proxy>& proxy, bool is_local_only) {
|
||||
return make_ready_future<>().then([this, is_local_only, &proxy] {
|
||||
return service::get_local_migration_manager().announce_new_column_family(get_cf_meta_data(proxy.local().get_db().local()), is_local_only);
|
||||
return make_ready_future<>().then([this, is_local_only] {
|
||||
return service::get_local_migration_manager().announce_new_column_family(get_cf_meta_data(), is_local_only);
|
||||
}).then_wrapped([this] (auto&& f) {
|
||||
try {
|
||||
f.get();
|
||||
@@ -122,13 +117,13 @@ future<shared_ptr<cql_transport::event::schema_change>> create_table_statement::
|
||||
* @return a CFMetaData instance corresponding to the values parsed from this statement
|
||||
* @throws InvalidRequestException on failure to validate parsed parameters
|
||||
*/
|
||||
schema_ptr create_table_statement::get_cf_meta_data(const database& db) {
|
||||
schema_builder builder{keyspace(), column_family(), _id};
|
||||
apply_properties_to(builder, db);
|
||||
schema_ptr create_table_statement::get_cf_meta_data() {
|
||||
schema_builder builder{keyspace(), column_family()};
|
||||
apply_properties_to(builder);
|
||||
return builder.build(_use_compact_storage ? schema_builder::compact_storage::yes : schema_builder::compact_storage::no);
|
||||
}
|
||||
|
||||
void create_table_statement::apply_properties_to(schema_builder& builder, const database& db) {
|
||||
void create_table_statement::apply_properties_to(schema_builder& builder) {
|
||||
auto&& columns = get_columns();
|
||||
for (auto&& column : columns) {
|
||||
builder.with_column(column);
|
||||
@@ -144,7 +139,7 @@ void create_table_statement::apply_properties_to(schema_builder& builder, const
|
||||
addColumnMetadataFromAliases(cfmd, Collections.singletonList(valueAlias), defaultValidator, ColumnDefinition.Kind.COMPACT_VALUE);
|
||||
#endif
|
||||
|
||||
_properties->apply_to_builder(builder, db.get_config().extensions());
|
||||
_properties->apply_to_builder(builder);
|
||||
}
|
||||
|
||||
void create_table_statement::add_column_metadata_from_aliases(schema_builder& builder, std::vector<bytes> aliases, const std::vector<data_type>& types, column_kind kind)
|
||||
@@ -164,16 +159,6 @@ create_table_statement::prepare(database& db, cql_stats& stats) {
|
||||
abort();
|
||||
}
|
||||
|
||||
future<> create_table_statement::grant_permissions_to_creator(const service::client_state& cs) {
|
||||
return do_with(auth::make_data_resource(keyspace(), column_family()), [&cs](const auth::resource& r) {
|
||||
return auth::grant_applicable_permissions(
|
||||
*cs.get_auth_service(),
|
||||
*cs.user(),
|
||||
r).handle_exception_type([](const auth::unsupported_authorization_operation&) {
|
||||
// Nothing.
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
create_table_statement::raw_statement::raw_statement(::shared_ptr<cf_name> name, bool if_not_exists)
|
||||
: cf_statement{std::move(name)}
|
||||
@@ -199,9 +184,9 @@ std::unique_ptr<prepared_statement> create_table_statement::raw_statement::prepa
|
||||
throw exceptions::invalid_request_exception(sprint("Multiple definition of identifier %s", (*i)->text()));
|
||||
}
|
||||
|
||||
_properties.validate(db.get_config().extensions());
|
||||
_properties.validate();
|
||||
|
||||
auto stmt = ::make_shared<create_table_statement>(_cf_name, _properties.properties(), _if_not_exists, _static_columns, _properties.properties()->get_id());
|
||||
auto stmt = ::make_shared<create_table_statement>(_cf_name, _properties.properties(), _if_not_exists, _static_columns);
|
||||
|
||||
std::experimental::optional<std::map<bytes, data_type>> defined_multi_cell_collections;
|
||||
for (auto&& entry : _definitions) {
|
||||
|
||||
@@ -90,13 +90,11 @@ class create_table_statement : public schema_altering_statement {
|
||||
column_set_type _static_columns;
|
||||
const ::shared_ptr<cf_prop_defs> _properties;
|
||||
const bool _if_not_exists;
|
||||
stdx::optional<utils::UUID> _id;
|
||||
public:
|
||||
create_table_statement(::shared_ptr<cf_name> name,
|
||||
::shared_ptr<cf_prop_defs> properties,
|
||||
bool if_not_exists,
|
||||
column_set_type static_columns,
|
||||
const stdx::optional<utils::UUID>& id);
|
||||
column_set_type static_columns);
|
||||
|
||||
virtual future<> check_access(const service::client_state& state) override;
|
||||
|
||||
@@ -106,9 +104,7 @@ public:
|
||||
|
||||
virtual std::unique_ptr<prepared> prepare(database& db, cql_stats& stats) override;
|
||||
|
||||
virtual future<> grant_permissions_to_creator(const service::client_state&) override;
|
||||
|
||||
schema_ptr get_cf_meta_data(const database&);
|
||||
schema_ptr get_cf_meta_data();
|
||||
|
||||
class raw_statement;
|
||||
|
||||
@@ -116,7 +112,7 @@ public:
|
||||
private:
|
||||
std::vector<column_definition> get_columns();
|
||||
|
||||
void apply_properties_to(schema_builder& builder, const database&);
|
||||
void apply_properties_to(schema_builder& builder);
|
||||
|
||||
void add_column_metadata_from_aliases(schema_builder& builder, std::vector<bytes> aliases, const std::vector<data_type>& types, column_kind kind);
|
||||
};
|
||||
|
||||
90
cql3/statements/create_user_statement.cc
Normal file
90
cql3/statements/create_user_statement.cc
Normal file
@@ -0,0 +1,90 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Copyright 2016 ScyllaDB
|
||||
*
|
||||
* Modified by ScyllaDB
|
||||
*/
|
||||
|
||||
/*
|
||||
* This file is part of Scylla.
|
||||
*
|
||||
* Scylla is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Scylla is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Scylla. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "create_user_statement.hh"
|
||||
#include "auth/authenticator.hh"
|
||||
#include "auth/service.hh"
|
||||
|
||||
cql3::statements::create_user_statement::create_user_statement(sstring username, ::shared_ptr<user_options> opts, bool superuser, bool if_not_exists)
|
||||
: _username(std::move(username))
|
||||
, _opts(std::move(opts))
|
||||
, _superuser(superuser)
|
||||
, _if_not_exists(if_not_exists)
|
||||
{}
|
||||
|
||||
void cql3::statements::create_user_statement::validate(distributed<service::storage_proxy>& proxy, const service::client_state& state) {
|
||||
if (_username.empty()) {
|
||||
throw exceptions::invalid_request_exception("Username can't be an empty string");
|
||||
}
|
||||
|
||||
_opts->validate(state.get_auth_service()->underlying_authenticator());
|
||||
|
||||
// validate login here before checkAccess to avoid leaking user existence to anonymous users.
|
||||
state.ensure_not_anonymous();
|
||||
|
||||
// cannot validate user existence compliant with _if_not_exists here, because
|
||||
// we need to query -> continuation, and this is not a continuation method
|
||||
}
|
||||
|
||||
future<::shared_ptr<cql_transport::messages::result_message>>
|
||||
cql3::statements::create_user_statement::execute(distributed<service::storage_proxy>& proxy, service::query_state& state, const query_options& options) {
|
||||
auto& client_state = state.get_client_state();
|
||||
auto& auth_service = *client_state.get_auth_service();
|
||||
|
||||
return auth::is_super_user(auth_service, *client_state.user()).then([this, &auth_service](bool is_super) {
|
||||
if (!is_super) {
|
||||
throw exceptions::unauthorized_exception("Only superusers are allowed to perform CREATE USER queries");
|
||||
}
|
||||
return auth_service.is_existing_user(_username).then([this, &auth_service](bool exists) {
|
||||
if (exists && !_if_not_exists) {
|
||||
throw exceptions::invalid_request_exception(sprint("User %s already exists", _username));
|
||||
}
|
||||
if (exists && _if_not_exists) {
|
||||
return make_ready_future<::shared_ptr<cql_transport::messages::result_message>>();
|
||||
}
|
||||
return auth_service.underlying_authenticator().create(_username, _opts->options()).then([this, &auth_service] {
|
||||
return auth_service.insert_user(_username, _superuser).then([] {
|
||||
return make_ready_future<::shared_ptr<cql_transport::messages::result_message>>();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -17,7 +17,7 @@
|
||||
*/
|
||||
|
||||
/*
|
||||
* Copyright 2017 ScyllaDB
|
||||
* Copyright 2016 ScyllaDB
|
||||
*
|
||||
* Modified by ScyllaDB
|
||||
*/
|
||||
@@ -41,30 +41,28 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <seastar/core/sstring.hh>
|
||||
|
||||
#include "cql3/role_name.hh"
|
||||
#include "cql3/statements/authorization_statement.hh"
|
||||
#include "seastarx.hh"
|
||||
#include "authentication_statement.hh"
|
||||
#include "cql3/user_options.hh"
|
||||
|
||||
namespace cql3 {
|
||||
|
||||
namespace statements {
|
||||
|
||||
class grant_role_statement final : public authorization_statement {
|
||||
sstring _role;
|
||||
|
||||
sstring _grantee;
|
||||
|
||||
class create_user_statement : public authentication_statement {
|
||||
private:
|
||||
sstring _username;
|
||||
::shared_ptr<user_options> _opts;
|
||||
bool _superuser;
|
||||
bool _if_not_exists;
|
||||
public:
|
||||
grant_role_statement(const cql3::role_name& name, const cql3::role_name& grantee)
|
||||
: _role(name.to_string()), _grantee(grantee.to_string()) {
|
||||
}
|
||||
|
||||
virtual future<> check_access(const service::client_state&) override;
|
||||
create_user_statement(sstring, ::shared_ptr<user_options>, bool superuser, bool if_not_exists);
|
||||
|
||||
virtual future<::shared_ptr<cql_transport::messages::result_message>>
|
||||
execute(distributed<service::storage_proxy>&, service::query_state&, const query_options&) override;
|
||||
void validate(distributed<service::storage_proxy>&, const service::client_state&) override;
|
||||
|
||||
future<::shared_ptr<cql_transport::messages::result_message>> execute(distributed<service::storage_proxy>&
|
||||
, service::query_state&
|
||||
, const query_options&) override;
|
||||
};
|
||||
|
||||
}
|
||||
@@ -155,7 +155,7 @@ future<shared_ptr<cql_transport::event::schema_change>> create_view_statement::a
|
||||
// - make sure there is not currently a table or view
|
||||
// - make sure base_table gc_grace_seconds > 0
|
||||
|
||||
_properties.validate(proxy.local().get_db().local().get_config().extensions());
|
||||
_properties.validate();
|
||||
|
||||
if (_properties.use_compact_storage()) {
|
||||
throw exceptions::invalid_request_exception(sprint(
|
||||
@@ -317,7 +317,7 @@ future<shared_ptr<cql_transport::event::schema_change>> create_view_statement::a
|
||||
add_columns(target_partition_keys, column_kind::partition_key);
|
||||
add_columns(target_clustering_keys, column_kind::clustering_key);
|
||||
add_columns(target_non_pk_columns, column_kind::regular_column);
|
||||
_properties.properties()->apply_to_builder(builder, proxy.local().get_db().local().get_config().extensions());
|
||||
_properties.properties()->apply_to_builder(builder);
|
||||
|
||||
if (builder.default_time_to_live().count() > 0) {
|
||||
throw exceptions::invalid_request_exception(
|
||||
|
||||
95
cql3/statements/drop_user_statement.cc
Normal file
95
cql3/statements/drop_user_statement.cc
Normal file
@@ -0,0 +1,95 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Copyright 2016 ScyllaDB
|
||||
*
|
||||
* Modified by ScyllaDB
|
||||
*/
|
||||
|
||||
/*
|
||||
* This file is part of Scylla.
|
||||
*
|
||||
* Scylla is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Scylla is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Scylla. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <boost/range/adaptor/map.hpp>
|
||||
|
||||
#include "drop_user_statement.hh"
|
||||
#include "auth/authenticator.hh"
|
||||
#include "auth/authorizer.hh"
|
||||
#include "auth/service.hh"
|
||||
|
||||
cql3::statements::drop_user_statement::drop_user_statement(sstring username, bool if_exists)
|
||||
: _username(std::move(username))
|
||||
, _if_exists(if_exists)
|
||||
{}
|
||||
|
||||
void cql3::statements::drop_user_statement::validate(distributed<service::storage_proxy>& proxy, const service::client_state& state) {
|
||||
// validate login here before checkAccess to avoid leaking user existence to anonymous users.
|
||||
state.ensure_not_anonymous();
|
||||
|
||||
// cannot validate user existence here, because
|
||||
// we need to query -> continuation, and this is not a continuation method
|
||||
|
||||
if (state.user()->name() == _username) {
|
||||
throw exceptions::invalid_request_exception("Users aren't allowed to DROP themselves");
|
||||
}
|
||||
}
|
||||
|
||||
future<::shared_ptr<cql_transport::messages::result_message>>
|
||||
cql3::statements::drop_user_statement::execute(distributed<service::storage_proxy>& proxy, service::query_state& state, const query_options& options) {
|
||||
auto& client_state = state.get_client_state();
|
||||
auto& auth_service = *client_state.get_auth_service();
|
||||
|
||||
return auth::is_super_user(auth_service, *client_state.user()).then([this, &auth_service](bool is_super) {
|
||||
if (!is_super) {
|
||||
throw exceptions::unauthorized_exception("Only superusers are allowed to perform DROP USER queries");
|
||||
}
|
||||
|
||||
return auth_service.is_existing_user(_username).then([this, &auth_service](bool exists) {
|
||||
if (!_if_exists && !exists) {
|
||||
throw exceptions::invalid_request_exception(sprint("User %s doesn't exist", _username));
|
||||
}
|
||||
if (_if_exists && !exists) {
|
||||
return make_ready_future<::shared_ptr<cql_transport::messages::result_message>>();
|
||||
}
|
||||
|
||||
// clean up permissions after the dropped user.
|
||||
return auth_service.underlying_authorizer().revoke_all(_username).then([this, &auth_service] {
|
||||
return auth_service.delete_user(_username).then([this, &auth_service] {
|
||||
return auth_service.underlying_authenticator().drop(_username);
|
||||
});
|
||||
}).then([] {
|
||||
return make_ready_future<::shared_ptr<cql_transport::messages::result_message>>();
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user