/Users/deen/code/yugabyte-db/src/yb/client/permissions.cc
Line | Count | Source (jump to first uncovered line) |
1 | | // Copyright (c) YugaByte, Inc. |
2 | | // |
3 | | // Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except |
4 | | // in compliance with the License. You may obtain a copy of the License at |
5 | | // |
6 | | // http://www.apache.org/licenses/LICENSE-2.0 |
7 | | // |
8 | | // Unless required by applicable law or agreed to in writing, software distributed under the License |
9 | | // is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express |
10 | | // or implied. See the License for the specific language governing permissions and limitations |
11 | | // under the License. |
12 | | // |
13 | | |
14 | | #include "yb/client/permissions.h" |
15 | | |
16 | | #include <atomic> |
17 | | |
18 | | #include "yb/client/client.h" |
19 | | |
20 | | #include "yb/master/master_dcl.pb.h" |
21 | | |
22 | | #include "yb/rpc/scheduler.h" |
23 | | |
24 | | #include "yb/util/result.h" |
25 | | |
26 | | DECLARE_int32(update_permissions_cache_msecs); |
27 | | |
28 | | namespace yb { |
29 | | namespace client { |
30 | | |
31 | | namespace internal { |
32 | | |
33 | | using yb::master::GetPermissionsResponsePB; |
34 | | |
35 | 17.4k | RolePermissions::RolePermissions(const master::RolePermissionInfoPB& role_permission_info_pb) { |
36 | 17.4k | DCHECK(role_permission_info_pb.has_all_keyspaces_permissions()); |
37 | 17.4k | DCHECK(role_permission_info_pb.has_all_roles_permissions()); |
38 | | |
39 | 17.4k | all_keyspaces_permissions_ = role_permission_info_pb.all_keyspaces_permissions(); |
40 | 17.4k | all_roles_permissions_ = role_permission_info_pb.all_roles_permissions(); |
41 | | |
42 | | // For each resource, extract its permissions and store it in the role's permissions map. |
43 | 20.8k | for (const auto &resource_permissions : role_permission_info_pb.resource_permissions()) { |
44 | 20.8k | DCHECK(resource_permissions.has_permissions()); |
45 | 0 | VLOG(1) << "Processing permissions " << resource_permissions.ShortDebugString(); |
46 | | |
47 | 20.8k | auto it = resource_permissions_.find(resource_permissions.canonical_resource()); |
48 | 20.8k | if (it == resource_permissions_.end()) { |
49 | 20.8k | resource_permissions_[resource_permissions.canonical_resource()] = |
50 | 20.8k | resource_permissions.permissions(); |
51 | 6 | } else { |
52 | | // permissions is a bitmap representing the permissions. |
53 | 6 | it->second |= resource_permissions.permissions(); |
54 | 6 | } |
55 | 20.8k | } |
56 | 17.4k | } |
57 | | |
58 | | bool RolePermissions::HasCanonicalResourcePermission(const std::string& canonical_resource, |
59 | 1.60k | PermissionType permission) const { |
60 | 1.60k | const auto& resource_permissions_itr = resource_permissions_.find(canonical_resource); |
61 | 1.60k | if (resource_permissions_itr == resource_permissions_.end()) { |
62 | 1.01k | return false; |
63 | 1.01k | } |
64 | 598 | const Permissions& resource_permissions_bitset = resource_permissions_itr->second; |
65 | 598 | return resource_permissions_bitset.test(permission); |
66 | 598 | } |
67 | | |
68 | 10.3k | bool RolePermissions::HasAllKeyspacesPermission(PermissionType permission) const { |
69 | 10.3k | return all_keyspaces_permissions_.test(permission); |
70 | 10.3k | } |
71 | | |
72 | 3.79k | bool RolePermissions::HasAllRolesPermission(PermissionType permission) const { |
73 | 3.79k | return all_roles_permissions_.test(permission); |
74 | 3.79k | } |
75 | | |
76 | | PermissionsCache::PermissionsCache(client::YBClient* client, |
77 | 1.45k | bool automatically_update_cache) : client_(client) { |
78 | 1.45k | if (!automatically_update_cache) { |
79 | 0 | return; |
80 | 0 | } |
81 | 1.45k | LOG(INFO) << "Creating permissions cache"; |
82 | 1.45k | pool_ = std::make_unique<yb::rpc::IoThreadPool>("permissions_cache_updater", 1); |
83 | 1.45k | scheduler_ = std::make_unique<yb::rpc::Scheduler>(&pool_->io_service()); |
84 | | // This will send many concurrent requests to the master for the permission data. |
85 | | // Queries done before a master leader is elected are all going to fail. This shouldn't be |
86 | | // an issue if the default refresh value is low enough. |
87 | | // TODO(hector): Add logic to retry failed requests so we don't depend on automatic |
88 | | // rescheduling to refresh the permissions cache. |
89 | 1.45k | ScheduleGetPermissionsFromMaster(true); |
90 | 1.45k | } |
91 | | |
92 | 0 | PermissionsCache::~PermissionsCache() { |
93 | 0 | if (pool_) { |
94 | 0 | scheduler_->Shutdown(); |
95 | 0 | pool_->Shutdown(); |
96 | 0 | pool_->Join(); |
97 | 0 | } |
98 | 0 | } |
99 | | |
100 | 0 | bool PermissionsCache::WaitUntilReady(MonoDelta wait_for) { |
101 | 0 | std::unique_lock<std::mutex> l(mtx_); |
102 | 0 | return cond_.wait_for(l, wait_for.ToSteadyDuration(), |
103 | 0 | [this] { return this->ready_.load(std::memory_order_acquire); }); |
104 | 0 | } |
105 | | |
106 | 119k | void PermissionsCache::ScheduleGetPermissionsFromMaster(bool now) { |
107 | 119k | if (FLAGS_update_permissions_cache_msecs <= 0) { |
108 | 0 | return; |
109 | 0 | } |
110 | | |
111 | 119k | DCHECK(pool_); |
112 | 119k | DCHECK(scheduler_); |
113 | | |
114 | 117k | scheduler_->Schedule([this](const Status &s) { |
115 | 117k | if (!s.ok()) { |
116 | 0 | LOG(INFO) << "Permissions cache updater scheduler was shutdown: " << s.ToString(); |
117 | 0 | return; |
118 | 0 | } |
119 | 117k | this->GetPermissionsFromMaster(); |
120 | 117k | }, std::chrono::milliseconds(now ? 0 : FLAGS_update_permissions_cache_msecs)); |
121 | 119k | } |
122 | | |
123 | 9.44k | void PermissionsCache::UpdateRolesPermissions(const GetPermissionsResponsePB& resp) { |
124 | 9.44k | auto new_roles_permissions_map = std::make_shared<RolesPermissionsMap>(); |
125 | 9.44k | auto new_roles_auth_info_map = std::make_shared<RolesAuthInfoMap>(); |
126 | | |
127 | | // Populate the cache. |
128 | | // Get all the roles in the response. They should have at least one piece of information: |
129 | | // the permissions for 'ALL ROLES' and 'ALL KEYSPACES' |
130 | 17.4k | for (const auto& role_permissions : resp.role_permissions()) { |
131 | 17.4k | auto result = new_roles_permissions_map->emplace(role_permissions.role(), |
132 | 17.4k | RolePermissions(role_permissions)); |
133 | 0 | LOG_IF(DFATAL, !result.second) << "Error inserting permissions for role " |
134 | 0 | << role_permissions.role(); |
135 | | |
136 | 17.4k | RoleAuthInfo role_auth_info; |
137 | 17.4k | role_auth_info.salted_hash = role_permissions.salted_hash(); |
138 | 17.4k | role_auth_info.can_login = role_permissions.can_login(); |
139 | 17.4k | auto result2 = new_roles_auth_info_map->emplace(role_permissions.role(), |
140 | 17.4k | std::move(role_auth_info)); |
141 | 0 | LOG_IF(DFATAL, !result2.second) << "Error inserting authentication info for role " |
142 | 0 | << role_permissions.role(); |
143 | 17.4k | } |
144 | | |
145 | 9.44k | { |
146 | 9.44k | std::unique_lock<simple_spinlock> l(permissions_cache_lock_); |
147 | | // It's possible that another thread already updated the cache with a more recent version. |
148 | 9.44k | if (version_ < resp.version()) { |
149 | 9.44k | std::atomic_store_explicit(&roles_permissions_map_, std::move(new_roles_permissions_map), |
150 | 9.44k | std::memory_order_release); |
151 | 9.44k | std::atomic_store_explicit(&roles_auth_info_map_, std::move(new_roles_auth_info_map), |
152 | 9.44k | std::memory_order_release); |
153 | | // Set the permissions cache's version. |
154 | 9.44k | version_ = resp.version(); |
155 | 9.44k | } |
156 | 9.44k | } |
157 | 9.44k | { |
158 | | // We need to hold the mutex before modifying ready_ to avoid a race condition with cond_. |
159 | 9.44k | std::lock_guard<std::mutex> l(mtx_); |
160 | 9.44k | ready_.store(true, std::memory_order_release); |
161 | 9.44k | } |
162 | 9.44k | cond_.notify_all(); |
163 | 9.44k | } |
164 | | |
165 | 117k | void PermissionsCache::GetPermissionsFromMaster() { |
166 | | // Schedule the cache update before we start processing anything because we want to stay as close |
167 | | // as possible to the refresh rate specified by the update_permissions_cache_msecs flag. |
168 | | // TODO(hector): Add a variable to track the last time that the cache was updated and have a |
169 | | // metric exposed for it, per-server. |
170 | 117k | ScheduleGetPermissionsFromMaster(false); |
171 | | |
172 | 117k | Status s = client_->GetPermissions(this); |
173 | | |
174 | 117k | if (!s.ok()) { |
175 | 0 | LOG(WARNING) << "Unable to refresh permissions cache. Received error: " << s.ToString(); |
176 | | // TODO(hector): If we received an error, then our cache will become stale. We need to allow |
177 | | // users to specify the max staleness that they are willing to tolerate. |
178 | | // For now it's safe to ignore the error since we always check |
179 | 0 | } |
180 | 117k | } |
181 | | |
182 | 519 | Result<std::string> PermissionsCache::salted_hash(const RoleName& role_name) { |
183 | 519 | std::shared_ptr<RolesAuthInfoMap> roles_auth_info_map; |
184 | 519 | roles_auth_info_map = std::atomic_load_explicit(&roles_auth_info_map_, |
185 | 519 | std::memory_order_acquire); |
186 | 519 | auto it = roles_auth_info_map->find(role_name); |
187 | 519 | if (it == roles_auth_info_map->end()) { |
188 | 121 | return STATUS(NotFound, "Role not found"); |
189 | 121 | } |
190 | 398 | return it->second.salted_hash; |
191 | 398 | } |
192 | | |
193 | 398 | Result<bool> PermissionsCache::can_login(const RoleName& role_name) { |
194 | 398 | std::shared_ptr<RolesAuthInfoMap> roles_auth_info_map; |
195 | 398 | roles_auth_info_map = std::atomic_load_explicit(&roles_auth_info_map_, |
196 | 398 | std::memory_order_acquire); |
197 | 398 | auto it = roles_auth_info_map->find(role_name); |
198 | 398 | if (it == roles_auth_info_map->end()) { |
199 | 0 | return STATUS(NotFound, "Role not found"); |
200 | 0 | } |
201 | 398 | return it->second.can_login; |
202 | 398 | } |
203 | | |
204 | | bool PermissionsCache::HasCanonicalResourcePermission(const std::string& canonical_resource, |
205 | | const ql::ObjectType& object_type, |
206 | | const RoleName& role_name, |
207 | 14.1k | const PermissionType& permission) { |
208 | 14.1k | std::shared_ptr<RolesPermissionsMap> roles_permissions_map; |
209 | 14.1k | roles_permissions_map = std::atomic_load_explicit(&roles_permissions_map_, |
210 | 14.1k | std::memory_order_acquire); |
211 | | |
212 | 14.1k | const auto& role_permissions_iter = roles_permissions_map->find(role_name); |
213 | 14.1k | if (role_permissions_iter == roles_permissions_map->end()) { |
214 | 0 | VLOG(1) << "Role " << role_name << " not found"; |
215 | | // Role doesn't exist. |
216 | 0 | return false; |
217 | 0 | } |
218 | | |
219 | | // Check if the requested permission has been granted to 'ALL KEYSPACES' or to 'ALL ROLES'. |
220 | 14.1k | const auto& role_permissions = role_permissions_iter->second; |
221 | 14.1k | if (object_type == ql::ObjectType::SCHEMA || object_type == ql::ObjectType::TABLE) { |
222 | 10.3k | if (role_permissions.HasAllKeyspacesPermission(permission)) { |
223 | | // Found. |
224 | 8.74k | return true; |
225 | 8.74k | } |
226 | 3.79k | } else if (object_type == ql::ObjectType::ROLE) { |
227 | 3.79k | if (role_permissions.HasAllRolesPermission(permission)) { |
228 | | // Found. |
229 | 3.56k | return true; |
230 | 3.56k | } |
231 | 1.84k | } |
232 | | |
233 | | // If we didn't find the permission by checking all_permissions, then the queried permission was |
234 | | // not granted to 'ALL KEYSPACES' or to 'ALL ROLES'. |
235 | 1.84k | if (canonical_resource == kRolesDataResource || canonical_resource == kRolesRoleResource) { |
236 | 233 | return false; |
237 | 233 | } |
238 | | |
239 | 1.60k | return role_permissions.HasCanonicalResourcePermission(canonical_resource, permission); |
240 | 1.60k | } |
241 | | |
242 | | |
243 | | } // namespace namespace internal |
244 | | } // namespace client |
245 | | } // namespace yb |