33#include < mutex>
44#include < stdexcept>
55
6- #include < aws/core/Aws.h>
7- #include < aws/rds/RDSClient.h>
6+ #include " dbconnector/defer.hpp"
87
8+ #include " duckdb/parser/keyword_helper.hpp"
99#include " postgres_secrets.hpp"
1010#include " postgres_utils.hpp"
1111
1212namespace duckdb {
1313
14- static std::mutex sdk_init_mutex;
15- static bool sdk_initialized = false ;
16- static Aws::SDKOptions sdk_options;
14+ bool PostgresAwsRdsTokenConfig::Enabled () {
15+ return !rds_secret_name.empty ();
16+ }
17+
18+ int64_t PostgresAwsRdsTokenConfig::MaxAgeSeconds () {
19+ return expiration_seconds - 60 ;
20+ }
21+
22+ static std::string MakeCreateSecretQuery (const std::string &template_secret_name, const std::string &secret_name,
23+ const KeyValueSecret &kv_secret) {
24+ std::string query (" CREATE OR REPLACE TEMPORARY SECRET \" " );
25+ query += secret_name;
26+ query += " \" (\n " ;
27+ query += " TYPE rds,\n " ;
28+ query += " PROVIDER credential_chain,\n " ;
29+ for (auto &en : kv_secret.secret_map ) {
30+ query += " " + en.first + " " + en.second .ToSQLString () + " ,\n " ;
31+ }
32+ query += " RDS_TEMPLATE_SECRET_NAME " + KeywordHelper::WriteQuoted (template_secret_name, ' \' ' ) + " \n " ;
33+ query += " )" ;
34+ return query;
35+ }
36+
37+ static void RunQuery (Connection &conn, const std::string &query, const std::string &err_msg = std::string()) {
38+ auto res = conn.Query (query);
39+ if (res->HasError ()) {
40+ if (err_msg.empty ()) {
41+ throw InvalidConfigurationException (" Error generating RDS IAM token: %s" , res->GetError ());
42+ } else {
43+ std::string error_type = EnumUtil::ToString (res->GetErrorType ());
44+ throw InvalidConfigurationException (" Error generating RDS IAM token, type: %s, %s" , error_type, err_msg);
45+ }
46+ }
47+ }
1748
18- static void EnsureAwsSdkInitialized ( ) {
19- std::lock_guard<std::mutex> lock (sdk_init_mutex );
20- if (!sdk_initialized) {
21- Aws::InitAPI (sdk_options);
22- sdk_initialized = true ;
49+ static unique_ptr<SecretEntry> GetRdsSecret (SecretManager &secret_manager, Connection &conn, const std::string &name ) {
50+ auto transaction = CatalogTransaction::GetSystemCatalogTransaction (*conn. context );
51+ auto secret_entry = secret_manager. GetSecretByName (transaction, name);
52+ if (!secret_entry) {
53+ throw InvalidConfigurationException ( " Specified RDS secret with name: \" %s \" not found " , name) ;
2354 }
55+ return secret_entry;
2456}
2557
26- std::string PostgresAws::GenerateRdsAuthToken (const PostgresAwsRdsTokenConfig &token_config) {
27- EnsureAwsSdkInitialized ();
28-
29- Aws::Client::ClientConfiguration config;
30- config.region = token_config.region ;
31- Aws::RDS ::RDSClient rds_client (config);
32-
33- // https://github.com/aws/aws-sdk-cpp/issues/861#issuecomment-386643571
34- // Aws::String token = rdsClient.GenerateConnectAuthToken(hostname.c_str(), aws_region.c_str(),
35- // static_cast<unsigned>(port_int), username.c_str());
36-
37- std::string host_and_port = token_config.hostname + " :" + token_config.port ;
38- std::string host_and_port_with_prefix = " http://" + host_and_port;
39- std::string host_and_port_with_suffix = host_and_port + " /" ;
40- Aws::Http::URI uri (host_and_port_with_prefix.c_str ());
41- uri.AddQueryStringParameter (" Action" , " connect" );
42- uri.AddQueryStringParameter (" DBUser" , token_config.username .c_str ());
43- auto token = rds_client.GeneratePresignedUrl (uri, Aws::Http::HttpMethod::HTTP_GET , token_config.region .c_str (),
44- " rds-db" , static_cast <long long >(token_config.expiration_seconds ));
45- Aws::Utils::StringUtils::Replace (token, host_and_port_with_prefix.c_str (), host_and_port_with_suffix.c_str ());
46-
47- std::string token_str = token.c_str ();
48- return token_str;
58+ std::string PostgresAws::GenerateRdsAuthToken (AttachedDatabase &attached_db,
59+ const PostgresAwsRdsTokenConfig &token_config) {
60+ DatabaseInstance &db = attached_db.GetDatabase ();
61+ Connection conn (db);
62+ RunQuery (conn, " LOAD aws" );
63+ RunQuery (conn, " BEGIN TRANSACTION" );
64+ auto deferred_rollback = dbconnector::Defer ([&conn] { conn.Query (" ROLLBACK" ); });
65+
66+ SecretManager &secret_manager = SecretManager::Get (db);
67+ auto template_secret = GetRdsSecret (secret_manager, conn, token_config.rds_secret_name );
68+ const auto &kv_template_secret = dynamic_cast <const KeyValueSecret &>(*template_secret->secret );
69+
70+ std::string secret_name = token_config.rds_secret_name + " _" + attached_db.GetName () + " _postgres_temp" ;
71+ std::string create_secret_query =
72+ MakeCreateSecretQuery (token_config.rds_secret_name , secret_name, kv_template_secret);
73+
74+ std::string quoted_secret_name = KeywordHelper::WriteQuoted (secret_name, ' "' );
75+ RunQuery (conn, create_secret_query, " error creating RDS secret from template: " + token_config.rds_secret_name );
76+ auto deferred_drop_secret =
77+ dbconnector::Defer ([&conn, quoted_secret_name] { conn.Query (" DROP SECRET " + quoted_secret_name); });
78+
79+ auto secret = GetRdsSecret (secret_manager, conn, secret_name);
80+ const auto &kv_secret = dynamic_cast <const KeyValueSecret &>(*secret->secret );
81+ std::string token = kv_secret.TryGetValue (" session_token" ).ToString ();
82+
83+ if (token.find (" X-Amz-Algorithm" ) == std::string::npos) {
84+ throw InvalidConfigurationException (" Unable to generate RDS IAM token for secret with name: \" %s\" " ,
85+ token_config.rds_secret_name );
86+ }
87+
88+ return token;
4989}
5090
5191static std::string ExtractString (const KeyValueSecret &kv, const std::string name) {
@@ -64,30 +104,16 @@ PostgresAwsRdsTokenConfig PostgresAws::ExtractTokenConfigFromSecret(optional_ptr
64104 return config;
65105 }
66106 const auto &kv = dynamic_cast <const KeyValueSecret &>(*secret_entry->secret );
67- Value enabled_val = kv.TryGetValue (" aws_rds_iam_auth_enabled" );
68- if (enabled_val.IsNull () || !BooleanValue::Get (enabled_val)) {
69- return config;
70- }
71107
72- config.enabled = true ;
73- config.hostname = ExtractString (kv, " host" );
74- config.port = ExtractString (kv, " port" );
75- config.username = ExtractString (kv, " user" );
76- config.region = ExtractString (kv, " aws_region" );
77- if (config.hostname .empty () || config.port .empty () || config.username .empty ()) {
78- throw BinderException (" Invalid AWS RDS IAM auth secret configuration: 'HOST', 'PORT', 'USER' and 'AWS_REGION' "
79- " parameters must be specified" );
108+ config.rds_secret_name = ExtractString (kv, " aws_rds_secret" );
109+ if (config.rds_secret_name .empty ()) {
110+ return config;
80111 }
81112
82113 std::string password = ExtractString (kv, " password" );
83114 if (!password.empty ()) {
84115 throw BinderException (" Invalid AWS RDS IAM auth secret configuration: 'PASSWORD' parameters must not be "
85- " specified - IAM token will be used instead of the password" );
86- }
87-
88- Value expiration_seconds_val = kv.TryGetValue (" aws_rds_iam_token_expiration_seconds" );
89- if (!expiration_seconds_val.IsNull ()) {
90- config.expiration_seconds = UBigIntValue::Get (expiration_seconds_val);
116+ " specified - generated IAM token will be used instead of the password" );
91117 }
92118
93119 // Build the base connection string (without password)
0 commit comments