redshift-iam 0.3.2

SAML/IAM authentication for Amazon Redshift via PingFederate or AWS STS
Documentation

redshift-iam

A Rust library for authenticating to Amazon Redshift using SAML-based single sign-on (SSO) or IAM temporary credentials. Inspired by the Amazon Redshift Python driver.

Overview

The authentication flow has three stages:

  1. SAML assertionPingCredentialsProvider logs in to a PingFederate IdP and retrieves a SAML assertion. This step is optional.
  2. IAM credentials — The assertion is exchanged for temporary AWS credentials via STS AssumeRoleWithSAML.
  3. Redshift credentials — The temporary AWS credentials are used to call GetClusterCredentials, obtaining a short-lived Redshift username/password.
  4. QueryRedshift connects using those credentials and executes queries, returning Arrow RecordBatches.

The query execution is only enabled if you include read_sql feature. Otherwise, you can get the connection_string and execute queries via other crates.

Usage

use std::collections::HashMap;
use secrecy::SecretString;
use redshift_iam::prelude::*;

let password = SecretString::new("my-password".to_string().into_boxed_str());

// 1. Obtain a SAML assertion from PingFederate and exchange it for AWS credentials
let ping_provider = PingCredentialsProvider::new(
    &HashMap::new(),         // optionally containing partnerspid key (empty map = default "urn:amazon:webservices")
    "pingfed.example.com",   // IdP host
    None,                    // IdP port (None = 443)
    "alice@example.com",     // username
    password,
);
let aws_credentials = ping_provider
    .get_credentials("arn:aws:iam::123456789012:role/RedshiftRole")
    .unwrap();

// 2. Exchange AWS credentials for Redshift cluster credentials
let (username, db_password) = IamProvider::new(
    "alice",                 // DB user
    "analytics",             // database
    "my-cluster",            // cluster identifier
    false,                   // auto-create user
)
// .set_region("eu-west-1") // optional, default: us-east-1
.auth(aws_credentials);

// 3. Connect and query
let conn = Redshift::new(username, db_password, "my-cluster.example.com", None, "analytics");
#[cfg(feature = "read_sql")]
let batches = conn.execute("SELECT * FROM my_table LIMIT 10").unwrap();

API

PingCredentialsProvider

Authenticates against a PingFederate IdP and retrieves temporary AWS credentials via SAML.

PingCredentialsProvider::new(
    partner_sp_id: &HashMap::new(),  // empty map -> "urn%3Aamazon%3Awebservices"
    idp_host: impl ToString,
    idp_port: Option<u16>,                 // None -> 443
    user_name: impl ToString,
    password: SecretString,
) -> Self
Method Description
get_credentials(role_arn) Full sync flow: SAML -> STS -> returns sts::types::Credentials
user() Returns the configured username
do_verify_ssl_cert() Returns true unless ssl_insecure is set
ssl_insecure: bool (pub field) Set to true to skip TLS verification

IamProvider

Exchanges temporary AWS credentials for Redshift cluster credentials.

IamProvider::new(user, database, cluster, autocreate) -> Self
Method Description
auth(aws_credentials) Calls GetClusterCredentials, returns (username, password)
set_region(region) Builder method to set the AWS region (default: us-east-1)
region() Returns the configured region

Redshift

Executes SQL queries against a Redshift cluster, returning Arrow RecordBatches.

Redshift::new(username, password, host, port: Option<u16>, database) -> Self
Method Description
execute(query) Runs the query and returns Vec<RecordBatch>
connection_string() Returns the URL-encoded connection string as a SecretString

Port defaults to 5439 if None is passed.

Custom SAML providers

You are not limited to PingFederate. Any type that implements the SamlProvider trait can be passed directly to the async get_credentials free function:

use redshift_iam::{SamlProvider, get_credentials};

struct MyIdpProvider { /* ... */ }

impl SamlProvider for MyIdpProvider {
    async fn get_saml_assertion(&self) -> String {
        // call your own IdP and return the base64-encoded SAMLResponse value
        todo!()
    }
}

let aws_credentials = get_credentials(
    &MyIdpProvider { /* ... */ },
    "arn:aws:iam::123456789012:role/RedshiftRole".to_string(),
)
.await
.unwrap();

The SamlProvider trait requires:

Item Description
async fn get_saml_assertion(&self) -> String Returns the base64-encoded SAML assertion

The live integration test (cargo test test_live_connection -- --ignored) reads credentials from the environment:

Variable Description
IDP_HOST PingFederate hostname
USER Redshift / IdP username
PWD Password
ROLE_ARN IAM role ARN to assume
CLUSTER Redshift cluster identifier
HOST Redshift cluster hostname
DATABASE Database name

Running tests

# Unit and mock-based tests only
cargo test

# Include the live integration test (requires env vars above)
HOST=... DATABASE=... USER=... PWD=... CLUSTER=... ROLE_ARN=... IDP_HOST=... \
  cargo test test_live_connection -- --ignored