YugabyteDB (2.13.1.0-b60, 21121d69985fbf76aa6958d8f04a9bfa936293b5)

Coverage Report

Created: 2022-03-22 16:43

/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.6k
RolePermissions::RolePermissions(const master::RolePermissionInfoPB& role_permission_info_pb) {
36
17.6k
  DCHECK(role_permission_info_pb.has_all_keyspaces_permissions());
37
17.6k
  DCHECK(role_permission_info_pb.has_all_roles_permissions());
38
39
17.6k
  all_keyspaces_permissions_ = role_permission_info_pb.all_keyspaces_permissions();
40
17.6k
  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.9k
  for (const auto &resource_permissions : role_permission_info_pb.resource_permissions()) {
44
20.9k
    DCHECK(resource_permissions.has_permissions());
45
18.4E
    VLOG(1) << "Processing permissions " << resource_permissions.ShortDebugString();
46
47
20.9k
    auto it = resource_permissions_.find(resource_permissions.canonical_resource());
48
20.9k
    if (it == resource_permissions_.end()) {
49
20.9k
      resource_permissions_[resource_permissions.canonical_resource()] =
50
20.9k
          resource_permissions.permissions();
51
20.9k
    } else {
52
      // permissions is a bitmap representing the permissions.
53
6
      it->second |= resource_permissions.permissions();
54
6
    }
55
20.9k
  }
56
17.6k
}
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.00k
    return false;
63
1.00k
  }
64
598
  const Permissions& resource_permissions_bitset = resource_permissions_itr->second;
65
598
  return resource_permissions_bitset.test(permission);
66
1.60k
}
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.46k
                                   bool automatically_update_cache) : client_(client) {
78
1.46k
  if (!automatically_update_cache) {
79
12
    return;
80
12
  }
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
1
PermissionsCache::~PermissionsCache() {
93
1
  if (pool_) {
94
0
    scheduler_->Shutdown();
95
0
    pool_->Shutdown();
96
0
    pool_->Join();
97
0
  }
98
1
}
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
118k
void PermissionsCache::ScheduleGetPermissionsFromMaster(bool now) {
107
118k
  if (FLAGS_update_permissions_cache_msecs <= 0) {
108
0
    return;
109
0
  }
110
111
118k
  DCHECK(pool_);
112
118k
  DCHECK(scheduler_);
113
114
118k
  scheduler_->Schedule([this](const Status &s) {
115
116k
    if (!s.ok()) {
116
0
      LOG(INFO) << "Permissions cache updater scheduler was shutdown: " << s.ToString();
117
0
      return;
118
0
    }
119
116k
    this->GetPermissionsFromMaster();
120
118k
  }, std::chrono::milliseconds(now ? 
01.45k
:
FLAGS_update_permissions_cache_msecs116k
));
121
118k
}
122
123
9.51k
void PermissionsCache::UpdateRolesPermissions(const GetPermissionsResponsePB& resp) {
124
9.51k
  auto new_roles_permissions_map = std::make_shared<RolesPermissionsMap>();
125
9.51k
  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.6k
  for (const auto& role_permissions : resp.role_permissions()) {
131
17.6k
    auto result = new_roles_permissions_map->emplace(role_permissions.role(),
132
17.6k
                                                     RolePermissions(role_permissions));
133
17.6k
    LOG_IF(DFATAL, !result.second) << "Error inserting permissions for role "
134
1
                                   << role_permissions.role();
135
136
17.6k
    RoleAuthInfo role_auth_info;
137
17.6k
    role_auth_info.salted_hash = role_permissions.salted_hash();
138
17.6k
    role_auth_info.can_login = role_permissions.can_login();
139
17.6k
    auto result2 = new_roles_auth_info_map->emplace(role_permissions.role(),
140
17.6k
                                                    std::move(role_auth_info));
141
17.6k
    LOG_IF(DFATAL, !result2.second) << "Error inserting authentication info for role "
142
0
                                   << role_permissions.role();
143
17.6k
  }
144
145
9.51k
  {
146
9.51k
    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.51k
    if (version_ < resp.version()) {
149
9.50k
      std::atomic_store_explicit(&roles_permissions_map_, std::move(new_roles_permissions_map),
150
9.50k
          std::memory_order_release);
151
9.50k
      std::atomic_store_explicit(&roles_auth_info_map_, std::move(new_roles_auth_info_map),
152
9.50k
          std::memory_order_release);
153
      // Set the permissions cache's version.
154
9.50k
      version_ = resp.version();
155
9.50k
    }
156
9.51k
  }
157
9.51k
  {
158
    // We need to hold the mutex before modifying ready_ to avoid a race condition with cond_.
159
9.51k
    std::lock_guard<std::mutex> l(mtx_);
160
9.51k
    ready_.store(true, std::memory_order_release);
161
9.51k
  }
162
9.51k
  cond_.notify_all();
163
9.51k
}
164
165
116k
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
116k
  ScheduleGetPermissionsFromMaster(false);
171
172
116k
  Status s = client_->GetPermissions(this);
173
174
116k
  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
116k
}
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
519
}
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::TABLE4.42k
) {
222
10.3k
    if (role_permissions.HasAllKeyspacesPermission(permission)) {
223
      // Found.
224
8.74k
      return true;
225
8.74k
    }
226
10.3k
  } else 
if (3.79k
object_type == ql::ObjectType::ROLE3.79k
) {
227
3.79k
    if (role_permissions.HasAllRolesPermission(permission)) {
228
      // Found.
229
3.56k
      return true;
230
3.56k
    }
231
3.79k
  }
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 == kRolesRoleResource1.62k
) {
236
236
    return false;
237
236
  }
238
239
1.60k
  return role_permissions.HasCanonicalResourcePermission(canonical_resource, permission);
240
1.84k
}
241
242
243
} // namespace namespace internal
244
} // namespace client
245
} // namespace yb