Skip to content

No documented method to construct an S3 client that overrides a CA bundle.  #1278

@russfellows

Description

@russfellows

Update.

I have been reading docs, and "chatting" with the AI gods. The program example is now closer to compiling. I use the EXACT same function as specified by Aaron above. That code is fine, it's just that there are still no examples of how to use that to build an actual functional S3 client. Note the following version that reports two errors from "cargo build". I will also attach my Cargo.toml file.

Cargo.toml:

[package]
name = "s3_custom"
version = "0.1.0"
edition = "2024"

[dependencies]
tokio = { version = "1.0", features = ["full"] }
aws-sdk-s3 = "1.82.0"
aws-smithy-client = "0.60.3"
clap = "4.5.36"
rustls = "0.23.26"
rustls-pemfile = "2.2.0"
hyper-rustls = "0.27.5"
tokio-rustls = "0.26.2"
aws-smithy-http-client = { version = "1.0.1", features = ["rustls-aws-lc"] }

Rust main.rs:

// src/main.rs
//
// This is intended to be a standalone, "simple" program that can demonstrate
// loading a custom certificate, and then performing a bucket list of an
// S3 provider, that uses a self signed certificate.
//
// This is close to the minimal set of code required to produce a usable
// example.  Currently it does not compile because of a lack of documentation
// and / or examples.
//
// Note: this is getting close, but still has two cargo build errors.  
// 1) The first on line 113: .rustls_connector(tls_context)
// 2) The second on line 119:  .endpoint_resolver(Endpoint::immutable 
//

use aws_sdk_s3::{Client, Config};
use aws_sdk_s3::config::{Credentials, Region};
use aws_sdk_s3::config::endpoint::Endpoint;
use aws_smithy_http_client::{
    tls::{self},
    Builder,
};
use clap::{Arg, Command};
use std::env;
use std::fs;

/// Function to create a TLS context from a PEM file (exactly as specified in the documentation).
fn tls_context_from_pem(filename: &str) -> tls::TlsContext {
    let pem_contents = fs::read(filename).unwrap();

    // Create a new empty trust store (this will not load platform native certificates)
    let trust_store = tls::TrustStore::empty()
        .with_pem_certificate(pem_contents.as_slice());

    tls::TlsContext::builder()
        .with_trust_store(trust_store)
        .build()
        .expect("valid TLS config")
}

/// Lists objects in the specified S3 bucket.
async fn list_bucket_objects(client: &Client, bucket: &str) {
    match client.list_objects_v2().bucket(bucket).send().await {
        Ok(output) => {
            if let Some(contents) = output.contents {
                println!("Objects in bucket '{}':", bucket);
                for object in contents {
                    if let Some(key) = object.key {
                        println!("- {}", key);
                    }
                }
            } else {
                println!("No objects found in bucket '{}'.", bucket);
            }
        }
        Err(err) => {
            eprintln!("Error listing objects in bucket '{}': {:?}", bucket, err);
        }
    }
}

#[tokio::main]
async fn main() {
    // Command-line argument parsing using Clap
    let matches = Command::new("S3 Client with Custom TLS")
        .arg(
            Arg::new("bucket")
                .short('b')
                .long("bucket")
                .value_parser(clap::value_parser!(String))
                .required(true)
                .help("The S3 bucket to list objects from"),
        )
        .arg(
            Arg::new("endpoint")
                .short('e')
                .long("endpoint")
                .value_parser(clap::value_parser!(String))
                .help("The custom S3 endpoint (e.g., http://localhost:9000)"),
        )
        .arg(
            Arg::new("cert")
                .short('c')
                .long("cert")
                .value_parser(clap::value_parser!(String))
                .required(true)
                .help("Path to the custom CA certificate file"),
        )
        .get_matches();

    // Parse command-line arguments
    let bucket = matches
        .get_one::<String>("bucket")
        .expect("Bucket name is required");
    let endpoint = matches
        .get_one::<String>("endpoint")
        .cloned()
        .unwrap_or_else(|| env::var("S3_ENDPOINT").unwrap_or_else(|_| "http://localhost:9000".to_string()));
    let cert_path = matches
        .get_one::<String>("cert")
        .expect("Certificate path is required");

    // Load AWS credentials from environment variables
    let aws_access_key_id = env::var("AWS_ACCESS_KEY_ID").expect("AWS_ACCESS_KEY_ID must be set");
    let aws_secret_access_key =
        env::var("AWS_SECRET_ACCESS_KEY").expect("AWS_SECRET_ACCESS_KEY must be set");

    // Create the TLS context using the custom CA certificate
    let tls_context = tls_context_from_pem(cert_path);

    // Create an AWS Smithy HTTP client builder and configure it
    let smithy_client = Builder::new()
        .rustls_connector(tls_context) // Reference: https://docs.rs/aws-smithy-http/0.53.0/aws_smithy_http/client/struct.Builder.html#method.rustls_connector
        .build();

    // Build the AWS SDK configuration
    let aws_config = Config::builder()
        .region(Region::new("us-east-1"))
        .endpoint_resolver(Endpoint::immutable(endpoint.parse().expect("Invalid endpoint URL"))) // Reference: https://docs.rs/aws-sdk-s3/1.82.0/aws_sdk_s3/config/endpoint/trait.ResolveEndpoint.html
        .credentials_provider(Credentials::new(
            aws_access_key_id,
            aws_secret_access_key,
            None,
            None,
            "custom",
        ))
        .build();

    // Create the S3 client using the custom HTTP connector
    let s3_client = Client::from_conf(aws_config);

    // List objects in the specified bucket
    list_bucket_objects(&s3_client, bucket).await;
}

Originally posted by @russfellows in #1277 (comment)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions