bes Updated for version 3.21.1
The Backend Server (BES) is the lower two tiers of the Hyrax data server
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 <string>
33#include <unordered_map>
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 "BESLog.h"
42#include "HttpNames.h"
43
44#include "CredentialsManager.h"
45
46using namespace std;
47
48#define prolog std::string("CredentialsManager::").append(__func__).append("() - ")
49
50#define CREDS "creds"
51
52namespace http {
53
54// Class vocabulary
55const char *CredentialsManager::ENV_ID_KEY = "CMAC_ID";
56const char *CredentialsManager::ENV_ACCESS_KEY = "CMAC_ACCESS_KEY";
57const char *CredentialsManager::ENV_REGION_KEY = "CMAC_REGION";
58const char *CredentialsManager::ENV_URL_KEY = "CMAC_URL";
59
60const char *CredentialsManager::USE_ENV_CREDS_KEY_VALUE = "ENV_CREDS";
61
64
66std::once_flag d_cmac_init_once;
67
68// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
69//
70// Helper Functions
71//
80std::string get_env_value(const string &key) {
81 string value;
82 const char *cstr = getenv(key.c_str());
83 if (cstr) {
84 value.assign(cstr);
85 BESDEBUG(CREDS, prolog << "From system environment - " << key << ": " << value << endl);
86 } else {
87 value.clear();
88 }
89 return value;
90}
91
92// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
93//
94// class CredentialsManager
95//
96
101CredentialsManager *CredentialsManager::theCM() {
102 std::call_once(d_cmac_init_once, CredentialsManager::initialize_instance);
103 return theMngr;
104}
105
106void CredentialsManager::initialize_instance() {
108 theMngr->load_credentials(); // Only call this here.
109#ifdef HAVE_ATEXIT
110 atexit(delete_instance);
111#endif
112}
113
114CredentialsManager::~CredentialsManager() {
115 for (auto &item: creds) {
116 delete item.second;
117 }
118 creds.clear();
119}
120
124void CredentialsManager::delete_instance() {
125 delete theMngr;
126 theMngr = nullptr;
127}
128
134void
135CredentialsManager::add(const std::string &key, AccessCredentials *ac) {
136 // This lock is a RAII implementation. It will block until the mutex is
137 // available and the lock will be released when the instance is destroyed.
138 std::lock_guard<std::recursive_mutex> lock_me(d_lock_mutex);
139
140 creds.insert(std::pair<std::string, AccessCredentials *>(key, ac));
141 BESDEBUG(HTTP_MODULE, prolog << "Added AccessCredentials to CredentialsManager.\n");
142 BESDEBUG(CREDS, prolog << "Credentials: \n" << ac->to_json() << "\n");
143}
144
152CredentialsManager::get(const shared_ptr <http::url> &url) {
153 // This lock is a RAII implementation. It will block until the mutex is
154 // available and the lock will be released when the instance is destroyed.
155 std::lock_guard<std::recursive_mutex> lock_me(d_lock_mutex);
156
157 AccessCredentials *best_match = nullptr;
158 std::string best_key;
159
160 if (url->protocol() == HTTP_PROTOCOL || url->protocol() == HTTPS_PROTOCOL) {
161 for (auto &item: creds) {
162 const std::string &key = item.first;
163 if ((url->str().rfind(key, 0) == 0) && (key.size() > best_key.size())) {
164 // url starts with key
165 best_key = key;
166 best_match = item.second;
167 }
168 }
169 }
170
171 return best_match;
172}
173
175CredentialsManager::get(const std::string &url) {
176 // Check the protocol before locking the credential manager. jhrg 2/20/25
177 const auto protocol = url.substr(0, url.find(':'));
178 if (url.find(HTTP_PROTOCOL) != 0 && url.find(HTTPS_PROTOCOL) != 0)
179 return nullptr;
180
181 // This lock is a RAII implementation. It will block until the mutex is
182 // available and the lock will be released when the instance is destroyed.
183 std::lock_guard<std::recursive_mutex> lock_me(d_lock_mutex);
184
185 AccessCredentials *best_match = nullptr;
186 for (auto &item: creds) {
187 std::string best_key;
188 const std::string &key = item.first;
189 if ((url.rfind(key, 0) == 0) && (key.size() > best_key.size())) {
190 // url starts with key
191 best_key = key;
192 best_match = item.second;
193 }
194 }
195
196 return best_match;
197}
198
199
205bool file_exists(const string &filename) {
206 struct stat buffer{};
207 return (stat(filename.c_str(), &buffer) == 0);
208}
209
230bool file_is_secured(const string &filename) {
231 struct stat st{};
232 if (stat(filename.c_str(), &st) != 0) {
233 string err;
234 err.append("file_is_secured() Unable to access file ");
235 err.append(filename).append(" strerror: ").append(strerror(errno));
236 throw BESInternalError(err, __FILE__, __LINE__);
237 }
238
239 mode_t perm = st.st_mode;
240 bool status;
241 status = (perm & S_IRUSR) && !(
242 // (perm & S_IWUSR) || // We don't need to enforce user no write
243 (perm & S_IXUSR) ||
244 (perm & S_IRGRP) ||
245 (perm & S_IWGRP) ||
246 (perm & S_IXGRP) ||
247 (perm & S_IROTH) ||
248 (perm & S_IWOTH) ||
249 (perm & S_IXOTH));
250 BESDEBUG(HTTP_MODULE,
251 prolog << "file_is_secured() " << filename << " secured: " << (status ? "true" : "false") << endl);
252 return status;
253}
254
289void CredentialsManager::load_credentials() {
290 string config_file;
291 bool found_key = true;
292 TheBESKeys::TheKeys()->get_value(CATALOG_MANAGER_CREDENTIALS, config_file, found_key);
293 if (!found_key) {
294 BESDEBUG(HTTP_MODULE, prolog << "The BES key " << CATALOG_MANAGER_CREDENTIALS
295 << " was not found in the BES configuration tree. No AccessCredentials were loaded"
296 << endl);
297 return;
298 }
299
300 // Does the configuration indicate that credentials will be submitted via the runtime environment?
301 if (config_file == string(CredentialsManager::USE_ENV_CREDS_KEY_VALUE)) {
302 // Apparently so...
303 auto *accessCredentials = load_credentials_from_env();
304 if (accessCredentials) {
305 // So if we have them, we add them to CredentialsManager object that called this method
306 // and then return without processing the configuration.
307 string url = accessCredentials->get(AccessCredentials::URL_KEY);
308 add(url, accessCredentials);
309 }
310 // Environment injected credentials override all other configuration credentials.
311 // Since the value of CATALOG_MANAGER_CREDENTIALS is ENV_CREDS_VALUE, there is no
312 // configuration file identified, so we simply return regardless of whether valid
313 // credential information was found in the ENV.
314 return;
315 }
316
317 if (!file_exists(config_file)) {
318 string err{prolog + "CredentialsManager config file "};
319 err += config_file + " was specified but is not present.\n"; // Bjarne says to use \n not endl. jhrg 8/25/23
320 ERROR_LOG(err);
321 BESDEBUG(HTTP_MODULE, err);
322 return;
323 }
324
325 if (!file_is_secured(config_file)) {
326 string err{prolog + "CredentialsManager config file "};
327 err += config_file + " is not secured! Set the access permissions to -rw------- (600) and try again.\n";
328 ERROR_LOG(err);
329 BESDEBUG(HTTP_MODULE, err);
330 return;
331 }
332
333 BESDEBUG(HTTP_MODULE, prolog << "The config file '" << config_file << "' is secured." << endl);
334
335 unordered_map<string, vector<string> > keystore;
336
337 kvp::load_keys(config_file, keystore);
338 map<string, AccessCredentials *> credential_sets;
339 AccessCredentials *accessCredentials = nullptr;
340 for (const auto &key: keystore) {
341 string creds_name = key.first;
342 const vector<string> &credentials_entries = key.second;
343 map<string, AccessCredentials *>::iterator mit;
344 mit = credential_sets.find(creds_name);
345 if (mit != credential_sets.end()) { // New?
346 // Nope.
347 accessCredentials = mit->second;
348 } else {
349 // Make new one
350 accessCredentials = new AccessCredentials(creds_name);
351 credential_sets.insert(pair<string, AccessCredentials *>(creds_name, accessCredentials));
352 }
353
354 for (const auto &entry: credentials_entries) {
355 size_t index = entry.find(':');
356 if (index > 0) {
357 string key_name = entry.substr(0, index);
358 string value = entry.substr(index + 1);
359 BESDEBUG(HTTP_MODULE, prolog << creds_name << ":" << key_name << "=" << value << endl);
360 accessCredentials->add(key_name, value);
361 }
362 }
363 }
364
365 BESDEBUG(HTTP_MODULE, prolog << "Loaded " << credential_sets.size() << " AccessCredentials" << endl);
366 vector<AccessCredentials *> bad_creds;
367
368 for (const auto &acit: credential_sets) {
369 accessCredentials = acit.second;
370 string url = accessCredentials->get(AccessCredentials::URL_KEY);
371 if (!url.empty()) {
372 add(url, accessCredentials);
373 } else {
374 bad_creds.push_back(acit.second);
375 }
376 }
377
378 if (!bad_creds.empty()) {
379 stringstream err;
380 err << "Encountered " << bad_creds.size() << " AccessCredentials "
381 << " definitions missing an associated URL. offenders: ";
382
383 for (auto &bc: bad_creds) {
384 err << bc->name() << " ";
385 credential_sets.erase(bc->name());
386 delete bc;
387 }
388 ERROR_LOG(err.str());
389 BESDEBUG(HTTP_MODULE, err.str());
390 return;
391 }
392 BESDEBUG(HTTP_MODULE, prolog << "Successfully ingested " << size() << " AccessCredentials" << endl);
393}
394
395
404AccessCredentials *CredentialsManager::load_credentials_from_env() {
405 std::lock_guard<std::recursive_mutex> lock_me(d_lock_mutex);
406
407 string env_id{get_env_value(CredentialsManager::ENV_ID_KEY)};
408 string env_access_key{get_env_value(CredentialsManager::ENV_ACCESS_KEY)};
409 string env_region{get_env_value(CredentialsManager::ENV_REGION_KEY)};
410 string env_url{get_env_value(CredentialsManager::ENV_URL_KEY)};
411
412 // evaluates to true iff none of the strings are empty. - ndp 08/07/23
413 if (!(env_url.empty() || env_id.empty() || env_access_key.empty() || env_region.empty())) {
414 auto ac = make_unique<AccessCredentials>();
415 ac->add(AccessCredentials::URL_KEY, env_url);
416 ac->add(AccessCredentials::ID_KEY, env_id);
417 ac->add(AccessCredentials::KEY_KEY, env_access_key);
418 ac->add(AccessCredentials::REGION_KEY, env_region);
419 return ac.release();
420 }
421 return nullptr;
422}
423
424} // namespace http
exception thrown if internal error encountered
void get_value(const std::string &s, std::string &val, bool &found)
Retrieve the value of a given key, if set.
static TheBESKeys * TheKeys()
Access to the singleton.
Definition TheBESKeys.cc:85
static CredentialsManager * theCM()
Returns the singleton instance of the CredentialsManager.
AccessCredentials * get(const std::shared_ptr< http::url > &url)
void add(const std::string &url, AccessCredentials *ac)
static CredentialsManager * theMngr
Our singleton instance.
Parse a URL into the protocol, host, path and query parts.
Definition url_impl.h:44
utility class for the HTTP catalog module
Definition TheBESKeys.h:51
std::once_flag d_cmac_init_once
Run once_flag for initializing the singleton instance.
bool file_is_secured(const string &filename)
std::string get_env_value(const string &key)
bool file_exists(const string &filename)