Drizzled Public API Documentation

module.cc
1 /* -*- mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; -*-
2  * vim:expandtab:shiftwidth=2:tabstop=2:smarttab:
3  *
4  * Copyright (C) 2010 Monty Taylor <mordred@inaugust.com>
5  * Copyright (C) 2011 Canonical, Ltd.
6  * Author: Clint Byrum <clint.byrum@canonical.com>
7  *
8  * Copied from simple_user_policy
9  *
10  * This program is free software; you can redistribute it and/or modify
11  * it under the terms of the GNU General Public License as published by
12  * the Free Software Foundation; version 2 of the License.
13  *
14  * This program is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17  * GNU General Public License for more details.
18  *
19  * You should have received a copy of the GNU General Public License
20  * along with this program; if not, write to the Free Software
21  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
22  */
23 
24 #include <config.h>
25 
26 #include <boost/foreach.hpp>
27 #include <boost/unordered_set.hpp>
28 #include <boost/thread/locks.hpp>
29 
30 #include <drizzled/item.h>
31 #include <drizzled/plugin/authorization.h>
33 
34 #include "policy.h"
35 
36 #include <fstream>
37 
38 namespace po= boost::program_options;
39 
40 using namespace std;
41 using namespace drizzled;
42 
43 namespace regex_policy
44 {
45 
46 uint64_t max_cache_buckets= DEFAULT_MAX_CACHE_BUCKETS;
47 uint64_t max_lru_length= DEFAULT_MAX_LRU_LENGTH;
48 bool updatePolicyFile(Session *, set_var *);
49 bool parsePolicyFile(std::string, PolicyItemList&, PolicyItemList&, PolicyItemList&);
50 Policy *policy= NULL;
51 
52 bool updatePolicyFile(Session *, set_var* var)
53 {
54  if (not var->value->str_value.empty())
55  {
56  std::string newPolicyFile(var->value->str_value.data());
57  if (policy->setPolicyFile(newPolicyFile))
58  return false; //success
59  else
60  return true; // error
61  }
62  errmsg_printf(error::ERROR, _("regex_policy file cannot be NULL"));
63  return true; // error
64 }
65 
66 bool parsePolicyFile(std::string new_policy_file, PolicyItemList& table_policies_dummy, PolicyItemList& schema_policies_dummy, PolicyItemList& process_policies_dummy)
67 {
68  ifstream file(new_policy_file.c_str());
69  boost::regex comment_re;
70  boost::regex empty_re;
71  boost::regex table_matches_re;
72  boost::regex process_matches_re;
73  boost::regex schema_matches_re;
74  table_policies_dummy.clear();
75  schema_policies_dummy.clear();
76  process_policies_dummy.clear();
77 
78  try
79  {
80  comment_re= comment_regex;
81  empty_re= empty_regex;
82  table_matches_re= table_match_regex;
83  process_matches_re= process_match_regex;
84  schema_matches_re= schema_match_regex;
85  }
86  catch (const std::exception &e)
87  {
88  errmsg_printf(error::ERROR, "%s", _(e.what()));
89  return false;
90  }
91 
92  if (! file.is_open())
93  {
94  string error_msg= "Unable to open regex policy file: " + new_policy_file;
95  errmsg_printf(error::ERROR, "%s", _(error_msg.c_str()));
96  return false;
97  }
98 
99  int lines= 0;
100  try
101  {
102  for (string line; getline(file, line); )
103  {
104  ++lines;
105  if (boost::regex_match(line, comment_re))
106  {
107  continue;
108  }
109  if (boost::regex_match(line, empty_re))
110  {
111  continue;
112  }
113  boost::smatch matches;
114  PolicyItemList *policies;
115  if (boost::regex_match(line, matches, table_matches_re, boost::match_extra))
116  {
117  policies= &table_policies_dummy;
118  }
119  else if (boost::regex_match(line, matches, process_matches_re, boost::match_extra))
120  {
121  policies= &process_policies_dummy;
122  }
123  else if (boost::regex_match(line, matches, schema_matches_re, boost::match_extra))
124  {
125  policies= &schema_policies_dummy;
126  }
127  else
128  {
129  throw std::exception();
130  }
131  string user_regex= matches[MATCH_REGEX_USER_POS];
132  string object_regex= matches[MATCH_REGEX_OBJECT_POS];
133  string action= matches[MATCH_REGEX_ACTION_POS];
134  try
135  {
136  policies->push_back(new PolicyItem(user_regex, object_regex, action));
137  }
138  catch (const std::exception &e)
139  {
140  string error_msg= "Bad policy item: user=" + user_regex + " object=" + object_regex + " action=" + action;
141  errmsg_printf(error::ERROR, "%s", _(error_msg.c_str()));
142  throw std::exception();
143  }
144  }
145  return true;
146  }
147  catch (const std::exception &e)
148  {
149  /* On any non-EOF break, unparseable line */
150  string error_msg= "Unable to parse policy file " + new_policy_file + ":" + e.what();
151  errmsg_printf(error::ERROR, "%s", _(error_msg.c_str()));
152  return false;
153  }
154 
155 }
156 
157 static int init(module::Context &context)
158 {
159  const module::option_map &vm= context.getOptions();
160 
161  max_cache_buckets= vm["max-cache-buckets"].as<uint64_t>();
162  if (max_cache_buckets < 1)
163  {
164  errmsg_printf(error::ERROR, _("max-cache-buckets is too low, must be greater than 0"));
165  return 1;
166  }
167  max_lru_length= vm["max-lru-length"].as<uint64_t>();
168  if (max_lru_length < 1)
169  {
170  errmsg_printf(error::ERROR, _("max-lru-length is too low, must be greater than 0"));
171  return 1;
172  }
173  policy= new Policy(vm["policy"].as<string>());
174  if (!policy->setPolicyFile(policy->getPolicyFile()))
175  {
176  errmsg_printf(error::ERROR, _("Could not load regex policy file: %s\n"),
177  (policy ? policy->getError().str().c_str() : _("Unknown")));
178  delete policy;
179  return 1;
180  }
181  context.add(policy);
182  context.registerVariable(new sys_var_std_string("policy", policy->getPolicyFile(), NULL, &updatePolicyFile));
183 
184  return 0;
185 }
186 
187 static void init_options(drizzled::module::option_context &context)
188 {
189  context("policy",
190  po::value<string>()->default_value(DEFAULT_POLICY_FILE.string()),
191  N_("File to load for regex authorization policies"));
192  context("max-cache-buckets",
193  po::value<uint64_t>()->default_value(DEFAULT_MAX_CACHE_BUCKETS),
194  N_("Maximum buckets for authorization cache"));
195  context("max-lru-length",
196  po::value<uint64_t>()->default_value(DEFAULT_MAX_LRU_LENGTH),
197  N_("Maximum number of LRU entries to track at once"));
198 }
199 
200 void Policy::setPolicies(PolicyItemList new_table_policies, PolicyItemList new_schema_policies, PolicyItemList new_process_policies)
201 {
202  policy->clearPolicies();
203 
204  for (PolicyItemList::iterator it= new_table_policies.begin(); it!= new_table_policies.end(); it++)
205  table_policies.push_back(*it);
206 
207  for (PolicyItemList::iterator it= new_schema_policies.begin(); it!= new_schema_policies.end(); it++)
208  schema_policies.push_back(*it);
209 
210  for (PolicyItemList::iterator it= new_process_policies.begin(); it!= new_process_policies.end(); it++)
211  process_policies.push_back(*it);
212 }
213 
214 std::string& Policy::getPolicyFile()
215 {
216  return sysvar_policy_file;
217 }
218 
219 bool Policy::setPolicyFile(std::string &new_policy_file)
220 {
221  if (new_policy_file.empty())
222  {
223  errmsg_printf(error::ERROR, _("regex_policy file cannot be an empty string"));
224  return false; // error
225  }
226 
227  PolicyItemList new_table_policies;
228  PolicyItemList new_schema_policies;
229  PolicyItemList new_process_policies;
230  if(parsePolicyFile(new_policy_file, new_table_policies, new_schema_policies, new_process_policies))
231  {
232  policy->setPolicies(new_table_policies, new_schema_policies, new_process_policies);
233  sysvar_policy_file= new_policy_file;
234  fs::path newPolicyFile(getPolicyFile());
235  policy_file= newPolicyFile;
236  return true; // success
237  }
238  return false; // error
239 }
240 
241 static void clearPolicyItemList(PolicyItemList& policies)
242 {
243  BOOST_FOREACH(PolicyItem* x, policies)
244  {
245  delete x;
246  }
247 }
248 
249 Policy::~Policy()
250 {
251  clearPolicyItemList(table_policies);
252  clearPolicyItemList(process_policies);
253  clearPolicyItemList(schema_policies);
254 }
255 
256 /*
257 This function will be called when the policy file needs to be reloaded.
258 This deletes all the policies stored and cached.
259 */
260 void Policy::clearPolicies()
261 {
262  table_policies.clear();
263  process_policies.clear();
264  schema_policies.clear();
265  table_check_cache.clear();
266  process_check_cache.clear();
267  schema_check_cache.clear();
268 }
269 
270 
271 bool Policy::restrictObject(const drizzled::identifier::User &user_ctx,
272  const string &obj, const PolicyItemList &policies,
273  CheckMap &check_cache)
274 {
275  CheckItem c(user_ctx.username(), obj, check_cache);
276  if (!c.hasCachedResult())
277  {
278  PolicyItemList::const_iterator m= find_if(policies.begin(), policies.end(), c);
279  if (m != policies.end())
280  {
281  c.setCachedResult((*m)->isRestricted());
282  }
283  else
284  {
285  /* TODO: make default action configurable */
286  c.setCachedResult(false);
287  }
288  }
289  return c.getCachedResult();
290 }
291 
292 bool Policy::restrictSchema(const drizzled::identifier::User &user_ctx,
293  const drizzled::identifier::Schema& schema)
294 {
295  return restrictObject(user_ctx, schema.getSchemaName(), schema_policies, schema_check_cache);
296 }
297 
298 bool Policy::restrictProcess(const drizzled::identifier::User &user_ctx,
299  const drizzled::identifier::User &session_ctx)
300 {
301  return restrictObject(user_ctx, session_ctx.username(), process_policies, process_check_cache);
302 }
303 
304 bool Policy::restrictTable(const drizzled::identifier::User& user_ctx,
305  const drizzled::identifier::Table& table)
306 {
307  return restrictObject(user_ctx, table.getTableName(), table_policies, table_check_cache);
308 }
309 
310 bool CheckItem::operator()(PolicyItem *p)
311 {
312  if (p->userMatches(user))
313  {
314  errmsg_printf(error::INSPECT, _("User %s matches regex\n"), user.c_str());
315  if (p->objectMatches(object))
316  {
317  errmsg_printf(error::INSPECT, _("Object %s matches regex %s (%s)\n"),
318  object.c_str(),
319  p->getObject().c_str(),
320  p->getAction());
321  return true;
322  }
323  errmsg_printf(error::INSPECT, _("Object %s NOT restricted by regex %s (%s)\n"),
324  object.c_str(),
325  p->getObject().c_str(),
326  p->getAction());
327  }
328  return false;
329 }
330 
331 CheckItem::CheckItem(const std::string &user_in, const std::string &obj_in, CheckMap &check_cache_in)
332  : user(user_in), object(obj_in), has_cached_result(false), check_cache(check_cache_in)
333 {
334  key= user + "_" + object;
335 
336  if (bool* check_val= check_cache.find(key))
337  {
338  /* It was in the cache, no need to do any more lookups */
339  cached_result= *check_val;
340  has_cached_result= true;
341  }
342 }
343 
344 bool* CheckMap::find(std::string const &k)
345 {
346  /* tack on to LRU list */
347  boost::mutex::scoped_lock lock(lru_mutex);
348  lru.push_back(k);
349  if (lru.size() > max_lru_length)
350  {
351  /* Fold all of the oldest entries into a single list at the front */
352  uint64_t size_halfway= lru.size() / 2;
353  LruList::iterator halfway= lru.begin();
354  halfway += size_halfway;
355  boost::unordered_set<std::string> uniqs;
356  uniqs.insert(lru.begin(), halfway);
357 
358  /* If we can save space, drop the oldest half */
359  if (size_halfway < uniqs.size())
360  {
361  lru.erase(lru.begin(), halfway);
362 
363  /* Re-add set elements to front */
364  lru.insert(lru.begin(), uniqs.begin(), uniqs.end());
365  }
366  }
367  lock.unlock();
368  boost::shared_lock<boost::shared_mutex> map_lock(map_mutex);
369  return find_ptr(map, k);
370 }
371 
372 void CheckMap::insert(std::string const &k, bool v)
373 {
374  boost::unique_lock<boost::shared_mutex> map_lock(map_mutex);
375  /* add our new hotness to the map */
376  map[k]=v;
377  /* Now prune if necessary */
378  if (map.bucket_count() > max_cache_buckets)
379  {
380  /* Determine LRU key by running through the LRU list */
381  boost::unordered_set<std::string> found;
382 
383  /* Must unfortunately lock the LRU list while we traverse it */
384  boost::mutex::scoped_lock lock(lru_mutex);
385  for (LruList::reverse_iterator x= lru.rbegin(); x < lru.rend(); ++x)
386  {
387  if (found.find(*x) == found.end())
388  {
389  /* Newly found key */
390  if (found.size() >= max_cache_buckets)
391  {
392  /* Since found is already as big as the cache can be, anything else
393  is LRU */
394  map.erase(*x);
395  }
396  else
397  {
398  found.insert(*x);
399  }
400  }
401  }
402  if (map.bucket_count() > max_cache_buckets)
403  {
404  /* Still too big. */
405  if (lru.size())
406  {
407  /* Just delete the oldest item */
408  map.erase(*(lru.begin()));
409  }
410  else
411  {
412  /* Nothing to delete, warn */
413  errmsg_printf(error::WARN,
414  _("Unable to reduce size of cache below max buckets (current buckets=%" PRIu64 ")"),
415  static_cast<uint64_t>(map.bucket_count()));
416  }
417  }
418  lru.clear();
419  lock.unlock();
420  }
421 }
422 
423 void CheckItem::setCachedResult(bool result)
424 {
425  check_cache.insert(key, result);
426  has_cached_result= true;
427  cached_result= result;
428 }
429 
430 } /* namespace regex_policy */
431 
432 DRIZZLE_DECLARE_PLUGIN
433 {
434  DRIZZLE_VERSION_ID,
435  "regex_policy",
436  "2.1",
437  "Clint Byrum",
438  N_("Authorization using a regex-matched policy file"),
439  PLUGIN_LICENSE_GPL,
440  regex_policy::init,
441  NULL,
442  regex_policy::init_options
443 }
444 DRIZZLE_DECLARE_PLUGIN_END;