bes  Updated for version 3.20.13
CredentialsManager.cc
1 // -*- mode: c++; c-basic-offset:4 -*-
2 
3 // This file is part of the BES
4 
5 // Copyright (c) 2020 OPeNDAP, Inc.
6 // Author: Nathan Potter<ndp@opendap.org>
7 //
8 // This library is free software; you can redistribute it and/or
9 // modify it under the terms of the GNU Lesser General Public
10 // License as published by the Free Software Foundation; either
11 // version 2.1 of the License, or (at your option) any later version.
12 //
13 // This library is distributed in the hope that it will be useful,
14 // but WITHOUT ANY WARRANTY; without even the implied warranty of
15 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 // Lesser General Public License for more details.
17 //
18 // You should have received a copy of the GNU Lesser General Public
19 // License along with this library; if not, write to the Free Software
20 // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
21 //
22 // You can contact OPeNDAP, Inc. at PO Box 112, Saunderstown, RI. 02874-0112.
23 // Created by ndp on 12/11/19.
24 //
25 
26 #include "config.h"
27 
28 #include <cstdlib>
29 #include <cstring>
30 #include <iomanip>
31 #include <sstream>
32 #include <locale>
33 #include <string>
34 #include <sys/stat.h>
35 
36 #include "AllowedHosts.h"
37 #include "TheBESKeys.h"
38 #include "kvp_utils.h"
39 #include "BESInternalError.h"
40 #include "BESDebug.h"
41 #include "CurlUtils.h"
42 #include "HttpNames.h"
43 
44 #include "CredentialsManager.h"
45 #include "NgapS3Credentials.h"
46 #include "DmrppNames.h"
47 
48 using namespace std;
49 
50 #define prolog std::string("CredentialsManager::").append(__func__).append("() - ")
51 
52 // Class vocabulary
53 const char *CredentialsManager::ENV_ID_KEY = "CMAC_ID";
54 const char *CredentialsManager::ENV_ACCESS_KEY = "CMAC_ACCESS_KEY";
55 const char *CredentialsManager::ENV_REGION_KEY = "CMAC_REGION";
56 const char *CredentialsManager::ENV_BUCKET_KEY = "CMAC_BUCKET";
57 const char *CredentialsManager::ENV_URL_KEY = "CMAC_URL";
58 
59 const char *CredentialsManager::USE_ENV_CREDS_KEY_VALUE = "ENV_CREDS";
60 
65 
69 static std::once_flag d_cmac_init_once;
70 
71 
72 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
73 //
74 // Helper Functions
75 //
84 std::string get_env_value(const string &key){
85  string value;
86  const char *cstr = getenv(key.c_str());
87  if(cstr){
88  value.assign(cstr);
89  BESDEBUG(CREDS, prolog << "From system environment - " << key << ": " << value << endl);
90  }
91  else {
92  value.clear();
93  }
94  return value;
95 }
96 
97 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
98 //
99 // class CredentialsManager
100 //
101 
107 
108  std::call_once(d_cmac_init_once,CredentialsManager::initialize_instance);
109  return theMngr;
110 }
111 
112 void CredentialsManager::initialize_instance()
113 {
114  theMngr = new CredentialsManager;
115 #ifdef HAVE_ATEXIT
116  atexit(delete_instance);
117 #endif
118 
119 }
120 
124 CredentialsManager::CredentialsManager(): ngaps3CredentialsLoaded(false){
125  // d_netrc_filename = curl::get_netrc_filename();
126 }
127 
128 
133  for (std::map<std::string, AccessCredentials *>::iterator it = creds.begin(); it != creds.end(); ++it) {
134  delete it->second;
135  }
136  creds.clear();
137 }
138 
139 
143 void CredentialsManager::delete_instance()
144 {
145  delete theMngr;
146  theMngr = 0;
147 }
148 
149 
155 void
156 CredentialsManager::add(const std::string &key, AccessCredentials *ac){
157  // This lock is a RAII implementation. It will block until the mutex is
158  // available and the lock will be released when the instance is destroyed.
159  std::lock_guard<std::recursive_mutex> lock_me(d_lock_mutex);
160 
161  creds.insert(std::pair<std::string,AccessCredentials *>(key, ac));
162  BESDEBUG(CREDS, prolog << "Added AccessCredentials to CredentialsManager. credentials: " << endl << ac->to_json() << endl);
163 }
164 
172 CredentialsManager::get(shared_ptr<http::url> &url){
173  // This lock is a RAII implementation. It will block until the mutex is
174  // available and the lock will be released when the instance is destroyed.
175  std::lock_guard<std::recursive_mutex> lock_me(d_lock_mutex);
176 
177  AccessCredentials *best_match = NULL;
178  std::string best_key("");
179 
180  if(url->protocol() == HTTP_PROTOCOL || url->protocol() == HTTPS_PROTOCOL) {
181  for (std::map<std::string, AccessCredentials *>::iterator it = creds.begin(); it != creds.end(); ++it) {
182  std::string key = it->first;
183  if (url->str().rfind(key, 0) == 0) {
184  // url starts with key
185  if (key.length() > best_key.length()) {
186  best_key = key;
187  best_match = it->second;
188  }
189  }
190  }
191  }
192  return best_match;
193 }
194 
200 bool file_exists(const string &filename) {
201  struct stat buffer;
202  return (stat (filename.c_str(), &buffer) == 0);
203 }
204 
225 bool file_is_secured(const string &filename) {
226  struct stat st;
227  if (stat(filename.c_str(), &st) != 0) {
228  string err;
229  err.append("file_is_secured() Unable to access file ");
230  err.append(filename).append(" strerror: ").append(strerror(errno));
231  throw BESInternalError(err, __FILE__, __LINE__);
232  }
233 
234  mode_t perm = st.st_mode;
235  bool status;
236  status = (perm & S_IRUSR) && !(
237  // (perm & S_IWUSR) || // We don't need to enforce user no write
238  (perm & S_IXUSR) ||
239  (perm & S_IRGRP) ||
240  (perm & S_IWGRP) ||
241  (perm & S_IXGRP) ||
242  (perm & S_IROTH) ||
243  (perm & S_IWOTH) ||
244  (perm & S_IXOTH));
245  BESDEBUG(CREDS, prolog << "file_is_secured() " << filename << " secured: " << (status ? "true" : "false") << endl);
246  return status;
247 }
248 
280 
281  // This lock is a RAII implementation. It will block until the mutex is
282  // available and the lock will be released when the instance is destroyed.
283  std::lock_guard<std::recursive_mutex> lock_me(d_lock_mutex);
284 
285  bool found_key = true;
286  AccessCredentials *accessCredentials;
287  map<string, AccessCredentials *> credential_sets;
288 
289  string config_file;
290  TheBESKeys::TheKeys()->get_value(CATALOG_MANAGER_CREDENTIALS, config_file, found_key);
291  if(!found_key){
292  BESDEBUG(CREDS, prolog << "The BES key " << CATALOG_MANAGER_CREDENTIALS
293  << " was not found in the BES configuration tree. No AccessCredentials were loaded" << endl);
294  return;
295  }
296 
297  // Does the configuration indicate that credentials will be submitted via the runtime environment?
298  if(config_file == string(CredentialsManager::USE_ENV_CREDS_KEY_VALUE)){
299  // Apparently so...
300  accessCredentials = theCM()->load_credentials_from_env();
301  if(accessCredentials){
302  // So if we have them, we add them to theCM() and then return without processing the configuration.
303  string url = accessCredentials->get(AccessCredentials::URL_KEY);
304  theCM()->add(url,accessCredentials);
305  }
306  // Environment injected credentials override all other configuration credentials.
307  // Since the value of CATALOG_MANAGER_CREDENTIALS is ENV_CREDS_VALUE there is no
308  // Configuration file identified, so wether or not valid credentials information was
309  // found in the ENV we simply return.
310  return;
311  }
312 
313  load_ngap_s3_credentials();
314 
315  if(!file_exists(config_file)){
316  BESDEBUG(CREDS, prolog << "The file specified by the BES key " << CATALOG_MANAGER_CREDENTIALS
317  << " does not exist. No Access Credentials were loaded." << endl);
318  return;
319  }
320 
321  if (!file_is_secured(config_file)) {
322  string err;
323  err.append("CredentialsManager config file ");
324  err.append(config_file);
325  err.append(" is not secured! ");
326  err.append("Set the access permissions to -rw------- (600) and try again.");
327  throw BESInternalError(err, __FILE__, __LINE__);
328  }
329  BESDEBUG(CREDS, prolog << "The config file '" << config_file << "' is secured." << endl);
330 
331  map <string, vector<string>> keystore;
332 
333  kvp::load_keys(config_file, keystore);
334 
335  for(map <string, vector<string>>::iterator it=keystore.begin(); it!=keystore.end(); it++) {
336  string creds_name = it->first;
337  vector<string> &credentials_entries = it->second;
338  map<string, AccessCredentials *>::iterator mit;
339  mit = credential_sets.find(creds_name);
340  if (mit != credential_sets.end()) { // New?
341  // Nope.
342  accessCredentials = mit->second;
343  } else {
344  // Make new one
345  accessCredentials = new AccessCredentials(creds_name);
346  credential_sets.insert(pair<string, AccessCredentials *>(creds_name, accessCredentials));
347  }
348  for (vector<string>::iterator jt = credentials_entries.begin(); jt != credentials_entries.end(); jt++) {
349  string credentials_entry = *jt;
350  int index = credentials_entry.find(":");
351  if (index > 0) {
352  string key_name = credentials_entry.substr(0, index);
353  string value = credentials_entry.substr(index + 1);
354  BESDEBUG(CREDS, prolog << creds_name << ":" << key_name << "=" << value << endl);
355  accessCredentials->add(key_name, value);
356  }
357  }
358  }
359  BESDEBUG(CREDS, prolog << "Loaded " << credential_sets.size() << " AccessCredentials" << endl);
360  vector<AccessCredentials *> bad_creds;
361  map<string,AccessCredentials *>::iterator acit;
362 
363  for (acit = credential_sets.begin(); acit != credential_sets.end(); acit++) {
364  accessCredentials = acit->second;
365  string url = accessCredentials->get(AccessCredentials::URL_KEY);
366  if(url.length()){
367  theCM()->add(url,accessCredentials);
368  }
369  else {
370  bad_creds.push_back(acit->second);
371  }
372  }
373  if(bad_creds.size()){
374  stringstream ss;
375  vector<AccessCredentials * >::iterator bc;
376 
377  ss << "Encountered " << bad_creds.size() << " AccessCredentials "
378  << " definitions missing an associated URL. offenders: ";
379 
380  for (bc = bad_creds.begin(); bc != bad_creds.end(); bc++) {
381  ss << (*bc)->name() << " ";
382  credential_sets.erase((*bc)->name());
383  delete *bc;
384  }
385  throw BESInternalError( ss.str(), __FILE__, __LINE__);
386  }
387  BESDEBUG(CREDS, prolog << "Successfully ingested " << theCM()->size() << " AccessCredentials" << endl);
388 
389 }
390 
391 
400 AccessCredentials *CredentialsManager::load_credentials_from_env( ) {
401 
402  // This lock is a RAII implementation. It will block until the mutex is
403  // available and the lock will be released when the instance is destroyed.
404  std::lock_guard<std::recursive_mutex> lock_me(d_lock_mutex);
405 
406  AccessCredentials *ac = nullptr;
407  string env_url, env_id, env_access_key, env_region, env_bucket;
408 
409  // If we are in developer mode then we compile this section which
410  // allows us to inject credentials via the system environment
411 
412  env_id.assign( get_env_value(CredentialsManager::ENV_ID_KEY));
413  env_access_key.assign(get_env_value(CredentialsManager::ENV_ACCESS_KEY));
414  env_region.assign( get_env_value(CredentialsManager::ENV_REGION_KEY));
415  //env_bucket.assign( get_env_value(CredentialsManager::ENV_BUCKET_KEY));
416  env_url.assign( get_env_value(CredentialsManager::ENV_URL_KEY));
417 
418  if(env_url.length() &&
419  env_id.length() &&
420  env_access_key.length() &&
421  // env_bucket.length() &&
422  env_region.length() ){
423  ac = new AccessCredentials();
424  ac->add(AccessCredentials::URL_KEY, env_url);
425  ac->add(AccessCredentials::ID_KEY, env_id);
426  ac->add(AccessCredentials::KEY_KEY, env_access_key);
427  ac->add(AccessCredentials::REGION_KEY, env_region);
428  // ac->add(AccessCredentials::BUCKET_KEY, env_bucket);
429  }
430  return ac;
431 }
432 
433 
434 std::string NGAP_S3_BASE_DEFAULT="https://";
439 void CredentialsManager::load_ngap_s3_credentials( ){
440  // This lock is a RAII implementation. It will block until the mutex is
441  // available and the lock will be released when the instance is destroyed.
442  std::lock_guard<std::recursive_mutex> lock_me(d_lock_mutex);
443 
444  string s3_distribution_endpoint_url;
445  bool found;
446  TheBESKeys::TheKeys()->get_value(NgapS3Credentials::BES_CONF_S3_ENDPOINT_KEY,s3_distribution_endpoint_url,found);
447  if(found) {
448  string value;
449 
450  long refresh_margin = 600;
451  TheBESKeys::TheKeys()->get_value(NgapS3Credentials::BES_CONF_REFRESH_KEY, value, found);
452  if (found) {
453  refresh_margin = strtol(value.c_str(), 0, 10);
454  }
455 
456  string s3_base_url = NGAP_S3_BASE_DEFAULT;
457  TheBESKeys::TheKeys()->get_value(NgapS3Credentials::BES_CONF_URL_BASE, value, found);
458  if (found) {
459  s3_base_url = value;
460  }
461 
462  NgapS3Credentials *nsc = new NgapS3Credentials(s3_distribution_endpoint_url, refresh_margin);
463  nsc->add(NgapS3Credentials::URL_KEY, s3_base_url);
464  nsc->name("NgapS3Credentials");
465 
466  CredentialsManager::theCM()->add(s3_base_url,nsc);
467  CredentialsManager::theCM()->ngaps3CredentialsLoaded = true;
468 
469  }
470  else {
471  BESDEBUG(CREDS,prolog << "WARNING: The BES configuration did not contain an instance of " <<
472  NgapS3Credentials::BES_CONF_S3_ENDPOINT_KEY <<
473  " NGAP S3 Credentials NOT loaded." << endl);
474  }
475 }
476 
void add(const std::string &key, const std::string &value)
Add the key and value pair.
virtual std::string get(const std::string &key)
exception thrown if internal error encountered
void add(const std::string &url, AccessCredentials *ac)
static CredentialsManager * theMngr
AccessCredentials * get(std::shared_ptr< http::url > &url)
static CredentialsManager * theCM()
Returns the singleton instance of the CrednetialsManager.
void get_value(const std::string &s, std::string &val, bool &found)
Retrieve the value of a given key, if set.
Definition: TheBESKeys.cc:340
static TheBESKeys * TheKeys()
Definition: TheBESKeys.cc:71