From f734eab9fdf678a13d096da71cb6206a4d13b944 Mon Sep 17 00:00:00 2001 From: justing-bq <62349012+justing-bq@users.noreply.github.com> Date: Wed, 12 Apr 2023 11:49:13 -0700 Subject: [PATCH] Parse Region from Secret if User does not provide Region (#134) --- .../using-the-aws-driver/UsingTheAwsDriver.md | 10 +++---- driver/secrets_manager_proxy.cc | 30 +++++++++++++++++-- driver/secrets_manager_proxy.h | 1 + .../secrets_manager_integration_test.cc | 16 +++++++++- unit_testing/secrets_manager_proxy_test.cc | 19 ++++++++++++ unit_testing/test_utils.cc | 4 +++ unit_testing/test_utils.h | 1 + 7 files changed, 72 insertions(+), 9 deletions(-) diff --git a/docs/using-the-aws-driver/UsingTheAwsDriver.md b/docs/using-the-aws-driver/UsingTheAwsDriver.md index 4f6a8c1ca..ea2a1fd65 100644 --- a/docs/using-the-aws-driver/UsingTheAwsDriver.md +++ b/docs/using-the-aws-driver/UsingTheAwsDriver.md @@ -311,9 +311,9 @@ IAM database authentication use is limited to certain database engines. For more | Option | Description | Type | Required | Default | | ---------------------------------- |------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|--------|----------|------------------------------------------| -| `AUTHENTICATION_MODE` | Set to `IAM` to enable IAM Authentication. | char* | No | Off | +| `AUTHENTICATION_MODE` | Set to `IAM` to enable IAM Authentication. | char* | Yes | Off | | `AWS_REGION` | Region used to generate IAM tokens. | char* | No | `us-east-1` -| `IAM_HOST` | Host URL for IAM authentication. URL must be a valid Amazon endpoint, and not a custom domain or an IP address. | char* | No | Empty | +| `IAM_HOST` | Host URL for IAM authentication. URL must be a valid Amazon endpoint, and not a custom domain or an IP address. | char* | Yes | Empty | | `IAM_PORT` | Port used for IAM authentication. | int | No | `3306` | | `IAM_EXPIRATION_TIME` | Amount of time in seconds before the generated IAM token expires. After expiration, future connections will require a new token to be generated. | int | No | `900` @@ -330,9 +330,9 @@ The following parameters are required for the AWS Secrets Manager Connection Plu | Option | Description | Type | Required | Default | | ---------------------------------- |------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|--------|----------|------------------------------------------| -| `AUTHENTICATION_MODE` | Set to `SECRETS MANAGER` to enable Secrets Manager Authentication. | char* | No | Off | -| `AWS_REGION` | Region of the secret. | char* | No | `us-east-1` -| `SECRET_ID` | Secret name or secret ARN. | char* | No | Empty +| `AUTHENTICATION_MODE` | Set to `SECRETS MANAGER` to enable Secrets Manager Authentication. | char* | Yes | Off | +| `AWS_REGION` | Region of the secret. | char* | Optional when secret id is ARN | `us-east-1` +| `SECRET_ID` | Secret name or secret ARN. | char* | Yes | Empty If you are working with the Windows DSN UI, click `Details >>` and navigate to the `AWS Authentication` tab to configure the parameters. diff --git a/driver/secrets_manager_proxy.cc b/driver/secrets_manager_proxy.cc index 2bd626238..2f8f7ccdc 100644 --- a/driver/secrets_manager_proxy.cc +++ b/driver/secrets_manager_proxy.cc @@ -29,6 +29,7 @@ #include #include +#include #include "aws_sdk_helper.h" #include "secrets_manager_proxy.h" @@ -43,6 +44,7 @@ namespace { AWS_SDK_HELPER SDK_HELPER; const Aws::String USERNAME_KEY{ "username" }; const Aws::String PASSWORD_KEY{ "password" }; + const std::string SECRETS_ARN_PATTERN{ "arn:aws:secretsmanager:([-a-zA-Z0-9]+):.*" }; } std::map, Aws::Utils::Json::JsonValue> SECRETS_MANAGER_PROXY::secrets_cache; @@ -51,12 +53,23 @@ std::mutex SECRETS_MANAGER_PROXY::secrets_cache_mutex; SECRETS_MANAGER_PROXY::SECRETS_MANAGER_PROXY(DBC* dbc, DataSource* ds) : CONNECTION_PROXY(dbc, ds) { ++SDK_HELPER; + const char* secret_ID = nullptr; + std::string region; + if (ds->auth_secret_id) { + secret_ID = ds_get_utf8attr(ds->auth_secret_id, &ds->auth_secret_id8); + } + if (ds->auth_region) { + region = ds_get_utf8attr(ds->auth_region, &ds->auth_region8); + } + + if (secret_ID && region.empty()) { + try_parse_region_from_secret(secret_ID, region); + } + SecretsManagerClientConfiguration config; - const auto region = ds_get_utf8attr(ds->auth_region, &ds->auth_region8); - config.region = region ? region : Aws::Region::US_EAST_1; + config.region = !region.empty() ? region : Aws::Region::US_EAST_1; this->sm_client = std::make_shared(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; } @@ -179,3 +192,14 @@ std::string SECRETS_MANAGER_PROXY::get_from_secret_json_value(std::string key) { } return value; } + +bool SECRETS_MANAGER_PROXY::try_parse_region_from_secret(std::string secret, std::string& region) { + std::regex rgx(SECRETS_ARN_PATTERN); + std::smatch matches; + if (std::regex_search(secret, matches, rgx) && matches.size() > 1) { + region = matches[1].str(); + return true; + } + + return false; +} diff --git a/driver/secrets_manager_proxy.h b/driver/secrets_manager_proxy.h index 1917dd975..d1798df6f 100644 --- a/driver/secrets_manager_proxy.h +++ b/driver/secrets_manager_proxy.h @@ -58,6 +58,7 @@ class SECRETS_MANAGER_PROXY : public CONNECTION_PROXY { bool fetch_latest_credentials(); bool parse_json_value(Aws::String json_string); std::string get_from_secret_json_value(std::string key); + static bool try_parse_region_from_secret(std::string secret, std::string& region); static std::map, Aws::Utils::Json::JsonValue> secrets_cache; static std::mutex secrets_cache_mutex; diff --git a/integration/secrets_manager_integration_test.cc b/integration/secrets_manager_integration_test.cc index 905480277..47c865120 100644 --- a/integration/secrets_manager_integration_test.cc +++ b/integration/secrets_manager_integration_test.cc @@ -83,7 +83,7 @@ class SecretsManagerIntegrationTest : public testing::Test { } }; -TEST_F(SecretsManagerIntegrationTest, EnableSecretsManager) { +TEST_F(SecretsManagerIntegrationTest, EnableSecretsManagerWithRegion) { connection_string = builder .withDSN(dsn) .withServer(MYSQL_CLUSTER_URL) @@ -98,6 +98,20 @@ TEST_F(SecretsManagerIntegrationTest, EnableSecretsManager) { EXPECT_EQ(SQL_SUCCESS, SQLDisconnect(dbc)); } +TEST_F(SecretsManagerIntegrationTest, EnableSecretsManagerWithoutRegion) { + connection_string = builder + .withDSN(dsn) + .withServer(MYSQL_CLUSTER_URL) + .withAuthMode("SECRETS MANAGER") + .withSecretId(SECRETS_ARN) + .build(); + SQLCHAR conn_out[4096] = "\0"; + SQLSMALLINT len; + + EXPECT_EQ(SQL_SUCCESS, SQLDriverConnect(dbc, nullptr, AS_SQLCHAR(connection_string.c_str()), SQL_NTS, conn_out, MAX_NAME_LEN, &len, SQL_DRIVER_NOPROMPT)); + EXPECT_EQ(SQL_SUCCESS, SQLDisconnect(dbc)); +} + TEST_F(SecretsManagerIntegrationTest, EnableSecretsManagerWrongRegion) { connection_string = builder .withDSN(dsn) diff --git a/unit_testing/secrets_manager_proxy_test.cc b/unit_testing/secrets_manager_proxy_test.cc index 3e755605b..5c8c5bff0 100644 --- a/unit_testing/secrets_manager_proxy_test.cc +++ b/unit_testing/secrets_manager_proxy_test.cc @@ -234,3 +234,22 @@ TEST_F(SecretsManagerProxyTest, TestFailedToGetSecrets) { EXPECT_FALSE(ret); EXPECT_EQ(0, TEST_UTILS::get_secrets_cache().size()); } + +TEST_F(SecretsManagerProxyTest, ParseRegionFromSecret) { + std::string region = ""; + EXPECT_TRUE(TEST_UTILS::try_parse_region_from_secret( + "arn:aws:secretsmanager:us-east-1:123456789012:secret:MyTestDatabaseSecret-a1b2c3", region)); + EXPECT_EQ("us-east-1", region); + + region = ""; + EXPECT_TRUE(TEST_UTILS::try_parse_region_from_secret( + "arn:aws:secretsmanager:us-west-3:0987654321:Whatever", region)); + EXPECT_EQ("us-west-3", region); + + region = ""; + EXPECT_FALSE(TEST_UTILS::try_parse_region_from_secret( + "arn:aws:secretmanager:us-east-1:123456789012:secret:MyTestDatabaseSecret-a1b2c3", region)); + EXPECT_EQ("", region); + + delete mock_connection_proxy; +} diff --git a/unit_testing/test_utils.cc b/unit_testing/test_utils.cc index a47466c22..dee55161c 100644 --- a/unit_testing/test_utils.cc +++ b/unit_testing/test_utils.cc @@ -126,3 +126,7 @@ void TEST_UTILS::clear_token_cache(IAM_PROXY* iam_proxy) { std::map, Aws::Utils::Json::JsonValue>& TEST_UTILS::get_secrets_cache() { return std::ref(SECRETS_MANAGER_PROXY::secrets_cache); } + +bool TEST_UTILS::try_parse_region_from_secret(std::string secret, std::string& region) { + return SECRETS_MANAGER_PROXY::try_parse_region_from_secret(secret, region); +} diff --git a/unit_testing/test_utils.h b/unit_testing/test_utils.h index f0fb8209e..dac4c68fa 100644 --- a/unit_testing/test_utils.h +++ b/unit_testing/test_utils.h @@ -58,6 +58,7 @@ class TEST_UTILS { static bool token_cache_contains_key(std::string cache_key); static void clear_token_cache(IAM_PROXY* iam_proxy); static std::map, Aws::Utils::Json::JsonValue>& get_secrets_cache(); + static bool try_parse_region_from_secret(std::string secret, std::string& region); }; #endif /* __TESTUTILS_H__ */