Skip to content

Commit

Permalink
Secrets Manager Proxy (#121)
Browse files Browse the repository at this point in the history
* new secrets_manager files

* working secrets manager client

* add cache for secrets manager proxy

* first unit test for secrets manager proxy

* added more unit tests

* fix unique lock missing template arguments

* fix multiple calls to aws api in secrets manager unit tests

* fix multiple aws init/shutdown api calls in integration tests

* fix calling init/shutdownApi in windows

* verbose log for windows community tests

* wrap initapi and shutdownapi inside secrets manager proxy contructor/destructor

* move init/shutdown aws api to alloc/free env handle

* move init/shutdown aws api to alloc/free env handle

* test

* test

* fix static variables

* fix atomic int init

* fix unit tests

* renaming

* cleanup

* introduce aws sdk helpoer

* address comments

* nit

* fix rebase

* fix rebase

* set error

* fix build

* better error message
  • Loading branch information
yanw-bq authored Mar 30, 2023
1 parent 07dc641 commit 165e902
Show file tree
Hide file tree
Showing 9 changed files with 498 additions and 5 deletions.
2 changes: 2 additions & 0 deletions driver/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ WHILE(${DRIVER_INDEX} LESS ${DRIVERS_COUNT})
prepare.cc
query_parsing.cc
results.cc
secrets_manager_proxy.cc
topology_service.cc
transact.cc
utility.cc)
Expand Down Expand Up @@ -140,6 +141,7 @@ WHILE(${DRIVER_INDEX} LESS ${DRIVERS_COUNT})
myutil.h
parse.h
query_parsing.h
secrets_manager_proxy.h
topology_service.h
../MYODBC_MYSQL.h ../MYODBC_CONF.h ../MYODBC_ODBC.h)
ENDIF(WIN32)
Expand Down
7 changes: 4 additions & 3 deletions driver/handle.cc
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@
#include "efm_proxy.h"
#include "iam_proxy.h"
#include "mysql_proxy.h"
#include "secrets_manager_proxy.h"

#include <mutex>

Expand Down Expand Up @@ -134,9 +135,9 @@ void DBC::init_proxy_chain(DataSource* dsrc)
head = iam_proxy;
}
else if (!myodbc_strcasecmp(AUTH_MODE_SECRETS_MANAGER, auth_mode)) {
// CONNECTION_PROXY* secrets_manager_proxy = new SECRETS_MANAGER_PROXY(his, dsrc);
// secrets_manager_proxy->set_next_proxy(head);
// head = secrets_manager_proxy;
CONNECTION_PROXY* secrets_manager_proxy = new SECRETS_MANAGER_PROXY(this, dsrc);
secrets_manager_proxy->set_next_proxy(head);
head = secrets_manager_proxy;
}
}

Expand Down
168 changes: 168 additions & 0 deletions driver/secrets_manager_proxy.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License, version 2.0
// (GPLv2), as published by the Free Software Foundation, with the
// following additional permissions:
//
// This program is distributed with certain software that is licensed
// under separate terms, as designated in a particular file or component
// or in the license documentation. Without limiting your rights under
// the GPLv2, the authors of this program hereby grant you an additional
// permission to link the program and your derivative works with the
// separately licensed software that they have included with the program.
//
// Without limiting the foregoing grant of rights under the GPLv2 and
// additional permission as to separately licensed software, this
// program is also subject to the Universal FOSS Exception, version 1.0,
// a copy of which can be found along with its FAQ at
// http://oss.oracle.com/licenses/universal-foss-exception.
//
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
// See the GNU General Public License, version 2.0, for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see
// http://www.gnu.org/licenses/gpl-2.0.html.

#include <aws/secretsmanager/SecretsManagerServiceClientModel.h>
#include <aws/secretsmanager/model/GetSecretValueRequest.h>

#include "aws_sdk_helper.h"
#include "secrets_manager_proxy.h"

#include "mylog.h"

#undef GetMessage // workaround for AWSError method GetMessage()

using namespace Aws::SecretsManager;

namespace {
AWS_SDK_HELPER SDK_HELPER;
const Aws::String USERNAME_KEY{ "username" };
const Aws::String PASSWORD_KEY{ "password" };
}

std::map<std::pair<Aws::String, Aws::String>, Aws::Utils::Json::JsonValue> SECRETS_MANAGER_PROXY::secrets_cache;
std::mutex SECRETS_MANAGER_PROXY::secrets_cache_mutex;

SECRETS_MANAGER_PROXY::SECRETS_MANAGER_PROXY(DBC* dbc, DataSource* ds) : CONNECTION_PROXY(dbc, ds) {
++SDK_HELPER;

SecretsManagerClientConfiguration config;
const auto region = ds_get_utf8attr(ds->auth_region, &ds->auth_region8);
config.region = region ? region : Aws::Region::US_EAST_1;
this->sm_client = std::make_shared<SecretsManagerClient>(config);

const auto secret_ID = ds_get_utf8attr(ds->auth_secret_id, &ds->auth_secret_id8);
this->secret_key = std::make_pair(secret_ID ? secret_ID : "", config.region);
this->next_proxy = nullptr;
}

#ifdef UNIT_TEST_BUILD
SECRETS_MANAGER_PROXY::SECRETS_MANAGER_PROXY(DBC* dbc, DataSource* ds, CONNECTION_PROXY* next_proxy,
std::shared_ptr<SecretsManagerClient> sm_client) :
CONNECTION_PROXY(dbc, ds), sm_client{std::move(sm_client)} {

const Aws::String region = ds_get_utf8attr(ds->auth_region, &ds->auth_region8);
const Aws::String secret_ID = ds_get_utf8attr(ds->auth_secret_id, &ds->auth_secret_id8);
this->secret_key = std::make_pair(secret_ID, region);
this->next_proxy = next_proxy;
}
#endif

SECRETS_MANAGER_PROXY::~SECRETS_MANAGER_PROXY() {
this->sm_client.reset();
--SDK_HELPER;
}

bool SECRETS_MANAGER_PROXY::connect(const char* host, const char* user, const char* passwd, const char* database,
unsigned int port, const char* unix_socket, unsigned long flags) {

if (this->secret_key.first.empty()) {
const auto error = "Missing required config parameter for Secrets Manager: Secret ID";
MYLOG_DBC_TRACE(dbc, error);
this->set_custom_error_message(error);
return false;
}

bool fetched = update_secret(false);
std::string username = get_from_secret_json_value(USERNAME_KEY);
std::string password = get_from_secret_json_value(PASSWORD_KEY);
fetched = false;
bool ret = next_proxy->connect(host, username.c_str(), password.c_str(), database, port, unix_socket, flags);

if (!ret && next_proxy->error_code() == ER_ACCESS_DENIED_ERROR && !fetched) {
// Login unsuccessful with cached credentials
// Try to re-fetch credentials and try again
fetched = update_secret(true);
if (fetched) {
username = get_from_secret_json_value(USERNAME_KEY);
password = get_from_secret_json_value(PASSWORD_KEY);
ret = next_proxy->connect(host, username.c_str(), password.c_str(), database, port, unix_socket, flags);
}
}

return ret;
}

bool SECRETS_MANAGER_PROXY::update_secret(bool force_re_fetch) {
bool fetched = false;
{
std::unique_lock<std::mutex> lock(secrets_cache_mutex);

const auto search = secrets_cache.find(this->secret_key);
if (search != secrets_cache.end() && !force_re_fetch) {
this->secret_json_value = search->second;
}
else {
this->secret_json_value = fetch_latest_credentials();
fetched = true;
secrets_cache[this->secret_key] = this->secret_json_value;
}
}

return fetched;
}

Aws::Utils::Json::JsonValue SECRETS_MANAGER_PROXY::fetch_latest_credentials() const {
Aws::String secret_string;

Model::GetSecretValueRequest request;
request.SetSecretId(this->secret_key.first);
auto get_secret_value_outcome = this->sm_client->GetSecretValue(request);

if (get_secret_value_outcome.IsSuccess()) {
secret_string = get_secret_value_outcome.GetResult().GetSecretString();
}
else {
MYLOG_DBC_TRACE(dbc, get_secret_value_outcome.GetError().GetMessage().c_str());
}
return parse_json_value(secret_string);
}

Aws::Utils::Json::JsonValue SECRETS_MANAGER_PROXY::parse_json_value(Aws::String json_string) const {
auto res_json = Aws::Utils::Json::JsonValue(json_string);
if (!res_json.WasParseSuccessful()) {
MYLOG_DBC_TRACE(dbc, res_json.GetErrorMessage().c_str());
throw std::runtime_error("Error parsing secrets manager response body. " + res_json.GetErrorMessage());
}
return res_json;
}

std::string SECRETS_MANAGER_PROXY::get_from_secret_json_value(std::string key) const {
std::string value;
const auto view = this->secret_json_value.View();

if (view.ValueExists(key)) {
value = view.GetString(key);
}
else {
const auto error = "Unable to extract the " + key + " from secrets manager response.";
MYLOG_DBC_TRACE(dbc, error.c_str());
throw std::runtime_error(error);
}
return value;
}
71 changes: 71 additions & 0 deletions driver/secrets_manager_proxy.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License, version 2.0
// (GPLv2), as published by the Free Software Foundation, with the
// following additional permissions:
//
// This program is distributed with certain software that is licensed
// under separate terms, as designated in a particular file or component
// or in the license documentation. Without limiting your rights under
// the GPLv2, the authors of this program hereby grant you an additional
// permission to link the program and your derivative works with the
// separately licensed software that they have included with the program.
//
// Without limiting the foregoing grant of rights under the GPLv2 and
// additional permission as to separately licensed software, this
// program is also subject to the Universal FOSS Exception, version 1.0,
// a copy of which can be found along with its FAQ at
// http://oss.oracle.com/licenses/universal-foss-exception.
//
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
// See the GNU General Public License, version 2.0, for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see
// http://www.gnu.org/licenses/gpl-2.0.html.

#ifndef __SECRETS_MANAGER_PROXY__
#define __SECRETS_MANAGER_PROXY__

#include <aws/core/Aws.h>
#include <aws/core/utils/json/JsonSerializer.h>
#include <aws/secretsmanager/SecretsManagerClient.h>
#include <map>

#include "connection_proxy.h"
#include "driver.h"

class SECRETS_MANAGER_PROXY : public CONNECTION_PROXY {
public:
SECRETS_MANAGER_PROXY(DBC* dbc, DataSource* ds);
#ifdef UNIT_TEST_BUILD
SECRETS_MANAGER_PROXY(DBC* dbc, DataSource* ds, CONNECTION_PROXY* next_proxy, std::shared_ptr<Aws::SecretsManager::SecretsManagerClient> sm_client);
#endif
~SECRETS_MANAGER_PROXY() override;

bool connect(const char* host, const char* user, const char* passwd, const char* database,
unsigned int port, const char* unix_socket, unsigned long flags) override;

private:
std::shared_ptr<Aws::SecretsManager::SecretsManagerClient> sm_client;
std::pair<Aws::String, Aws::String> secret_key;
Aws::Utils::Json::JsonValue secret_json_value;

bool update_secret(bool force_re_fetch);
Aws::Utils::Json::JsonValue fetch_latest_credentials() const;
Aws::Utils::Json::JsonValue parse_json_value(Aws::String json_string) const;
std::string get_from_secret_json_value(std::string key) const;

static std::map<std::pair<Aws::String, Aws::String>, Aws::Utils::Json::JsonValue> secrets_cache;
static std::mutex secrets_cache_mutex;

#ifdef UNIT_TEST_BUILD
// Allows for testing private/protected methods
friend class TEST_UTILS;
#endif
};

#endif /* __SECRETS_MANAGER_PROXY__ */
1 change: 1 addition & 0 deletions unit_testing/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ add_executable(
multi_threaded_monitor_service_test.cc
query_parsing_test.cc
main.cc
secrets_manager_proxy_test.cc
topology_service_test.cc
)

Expand Down
10 changes: 10 additions & 0 deletions unit_testing/mock_objects.h
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
#ifndef __MOCKOBJECTS_H__
#define __MOCKOBJECTS_H__

#include <aws/secretsmanager/SecretsManagerClient.h>
#include <gmock/gmock.h>

#include "driver/connection_proxy.h"
Expand Down Expand Up @@ -83,6 +84,8 @@ class MOCK_CONNECTION_PROXY : public CONNECTION_PROXY {
MOCK_METHOD(void, init, ());
MOCK_METHOD(int, ping, ());
MOCK_METHOD(void, delete_ds, ());
MOCK_METHOD(bool, connect, (const char*, const char*, const char*, const char*, unsigned int, const char*, unsigned long));
MOCK_METHOD(unsigned int, error_code, ());
};

class MOCK_IAM_PROXY : public IAM_PROXY {
Expand Down Expand Up @@ -203,4 +206,11 @@ class MOCK_MONITOR_SERVICE : public MONITOR_SERVICE {
MOCK_METHOD(void, stop_monitoring_for_all_connections, (std::set<std::string>));
};

class MOCK_SECRETS_MANAGER_CLIENT : public Aws::SecretsManager::SecretsManagerClient {
public:
MOCK_SECRETS_MANAGER_CLIENT() : SecretsManagerClient() {};

MOCK_METHOD(Aws::SecretsManager::Model::GetSecretValueOutcome, GetSecretValue, (const Aws::SecretsManager::Model::GetSecretValueRequest&), (const));
};

#endif /* __MOCKOBJECTS_H__ */
Loading

0 comments on commit 165e902

Please sign in to comment.