Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@ endif()
include_directories(
include
database-connector/src/include
${OPENSSL_INCLUDE_DIR})
${OPENSSL_INCLUDE_DIR}
${PostgreSQL_INCLUDE_DIRS})

if(WIN32)
include_directories(
Expand Down
47 changes: 47 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,53 @@ D SELECT * FROM postgres_db.uuids;

For more information on how to use the connector, refer to the [Postgres documentation on the website](https://duckdb.org/docs/extensions/postgres).

### AWS RDS IAM Authentication

The extension supports AWS RDS IAM-based authentication, which allows you to connect to RDS PostgreSQL instances using IAM database authentication instead of static passwords. This feature automatically generates temporary authentication tokens using the AWS CLI.

#### Requirements

- AWS CLI installed and available on `PATH` (works on Linux, macOS, and Windows)
- RDS instance with IAM database authentication enabled
- IAM user/role with `rds-db:connect` permission for the RDS instance
- AWS credentials configured (via `AWS_PROFILE`, `AWS_ACCESS_KEY_ID`/`AWS_SECRET_ACCESS_KEY`, or IAM role)

#### Usage

To use RDS IAM authentication, create a Postgres secret with the `USE_RDS_IAM_AUTH` parameter set to `true`:

```sql
CREATE SECRET rds_secret (
TYPE POSTGRES,
HOST 'my-db-instance.xxxxxx.us-west-2.rds.amazonaws.com',
PORT '5432',
USER 'my_iam_user',
DATABASE 'mydb',
USE_RDS_IAM_AUTH true,
AWS_REGION 'us-west-2' -- Optional: uses AWS CLI default if not specified
);

ATTACH '' AS rds_db (TYPE POSTGRES, SECRET rds_secret);
```

#### Secret Parameters for RDS IAM Authentication

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `USE_RDS_IAM_AUTH` | BOOLEAN | Yes | Enable RDS IAM authentication |
| `HOST` | VARCHAR | Yes | RDS instance hostname |
| `PORT` | VARCHAR | Yes | RDS instance port (typically 5432) |
| `USER` | VARCHAR | Yes | IAM database username |
| `DATABASE` or `DBNAME` | VARCHAR | No | Database name |
| `AWS_REGION` | VARCHAR | No | AWS region (optional, uses AWS CLI default if not specified) |

#### Important Notes

- **Token Expiration**: RDS auth tokens expire after 15 minutes. The extension caches tokens for 13 minutes per `(host, port, user, region)` tuple, so new pool connections reuse cached tokens rather than spawning a new `aws` process each time.
- **AWS CLI**: The extension uses the `aws rds generate-db-auth-token` command. Make sure the AWS CLI is installed and configured with appropriate credentials.
- **Environment Variables**: The AWS CLI command inherits environment variables from the parent process, so `AWS_PROFILE`, `AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`, `AWS_SESSION_TOKEN`, and `AWS_REGION` will be available to the AWS CLI.


## Building & Loading the Extension

The DuckDB submodule must be initialized prior to building.
Expand Down
1 change: 1 addition & 0 deletions src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ add_subdirectory(storage)

add_library(
postgres_ext_library OBJECT
process_exec.cpp
postgres_attach.cpp
postgres_binary_copy.cpp
postgres_binary_file_reader.cpp
Expand Down
18 changes: 18 additions & 0 deletions src/include/process_exec.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
#pragma once

#include "duckdb/common/string.hpp"
#include "duckdb/common/vector.hpp"

namespace duckdb {

struct ProcessResult {
int exit_code;
string stdout_str;
string stderr_str;
};

// Executes argv[0] (resolved via PATH) with args argv[1..]. No shell interpretation.
// Throws IOException if the process cannot be spawned or exceeds the 30-second timeout.
ProcessResult RunProcess(const vector<string> &argv);

} // namespace duckdb
12 changes: 11 additions & 1 deletion src/include/storage/postgres_catalog.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ class PostgresCatalog : public Catalog {
public:
explicit PostgresCatalog(AttachedDatabase &db_p, string connection_string, string attach_path,
AccessMode access_mode, string schema_to_load, PostgresIsolationLevel isolation_level,
ClientContext &context);
ClientContext &context, string secret_name = string());
~PostgresCatalog();

string connection_string;
Expand All @@ -43,6 +43,8 @@ class PostgresCatalog : public Catalog {

static string GetConnectionString(ClientContext &context, const string &attach_path, string secret_name);

string GetFreshConnectionString();

optional_ptr<CatalogEntry> CreateSchema(CatalogTransaction transaction, CreateSchemaInfo &info) override;

void ScanSchemas(ClientContext &context, std::function<void(SchemaCatalogEntry &)> callback) override;
Expand Down Expand Up @@ -111,6 +113,14 @@ class PostgresCatalog : public Catalog {
PostgresSchemaSet schemas;
shared_ptr<PostgresConnectionPool> connection_pool;
string default_schema;

string secret_name;
bool use_rds_iam_auth = false;
string rds_hostname;
string rds_port;
string rds_username;
string rds_region;
string rds_base_connection_string;
};

} // namespace duckdb
6 changes: 6 additions & 0 deletions src/postgres_extension.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,10 @@ unique_ptr<BaseSecret> CreatePostgresSecretFunction(ClientContext &context, Crea
result->secret_map["port"] = named_param.second.ToString();
} else if (lower_name == "passfile") {
result->secret_map["passfile"] = named_param.second.ToString();
} else if (lower_name == "use_rds_iam_auth") {
result->secret_map["use_rds_iam_auth"] = named_param.second;
} else if (lower_name == "aws_region") {
result->secret_map["aws_region"] = named_param.second.ToString();
} else {
throw InternalException("Unknown named parameter passed to CreatePostgresSecretFunction: " + lower_name);
}
Expand All @@ -135,6 +139,8 @@ void SetPostgresSecretParameters(CreateSecretFunction &function) {
function.named_parameters["database"] = LogicalType::VARCHAR; // alias for dbname
function.named_parameters["dbname"] = LogicalType::VARCHAR;
function.named_parameters["passfile"] = LogicalType::VARCHAR;
function.named_parameters["use_rds_iam_auth"] = LogicalType::BOOLEAN;
function.named_parameters["aws_region"] = LogicalType::VARCHAR;
}

void SetPostgresNullByteReplacement(ClientContext &context, SetScope scope, Value &parameter) {
Expand Down
3 changes: 2 additions & 1 deletion src/postgres_storage.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,8 @@ static unique_ptr<Catalog> PostgresAttach(optional_ptr<StorageExtensionInfo> sto
}
auto connection_string = PostgresCatalog::GetConnectionString(context, attach_path, secret_name);
return make_uniq<PostgresCatalog>(db, std::move(connection_string), std::move(attach_path),
attach_options.access_mode, std::move(schema_to_load), isolation_level, context);
attach_options.access_mode, std::move(schema_to_load), isolation_level, context,
std::move(secret_name));
}

static unique_ptr<TransactionManager> PostgresCreateTransactionManager(optional_ptr<StorageExtensionInfo> storage_info,
Expand Down
Loading
Loading